Skip to content

feat: add two- and three-page layouts for ultrawide displays - #67

Closed
popeye001 wants to merge 5 commits into
marswaveai:mainfrom
popeye001:feat/ultrawide-page-layouts
Closed

feat: add two- and three-page layouts for ultrawide displays#67
popeye001 wants to merge 5 commits into
marswaveai:mainfrom
popeye001:feat/ultrawide-page-layouts

Conversation

@popeye001

@popeye001 popeye001 commented Sep 1, 2026

Copy link
Copy Markdown

Summary

  • add View > Page Layout options for continuous, two-page, and three-page reading
  • preserve the existing 780px maximum line length while using ultrawide space for additional pages
  • turn vertical mouse-wheel input and Page Up / Page Down into whole-spread navigation
  • leave horizontal gestures available to constrained content and re-snap outer scrolling to the nearest complete spread after input settles
  • keep search results, outline navigation, document anchors, source-mode transitions, and code-copy controls aligned with horizontal paging
  • constrain tables to one page width and continue long tables between complete rows instead of painting over adjacent pages

Design

The editor remains a single ProseMirror document. CSS columns provide the visual pagination, so Markdown serialization, editing, autosave, file watching, and agent sync continue to use the existing document model. The selected layout is persisted locally and continuous scrolling remains the default.

Paged layout classes are excluded from HTML/image export snapshots, and print styles explicitly restore the existing single-column export layout.

Review follow-up

The requested changes from the first review are now on the branch:

  • route in-document anchor jumps through the paged-layout reveal controller
  • add a scroller-level idle re-snap path for native, ProseMirror, selection-drag, and programmatic scrolling
  • preserve the final partial spread as a valid snap destination
  • release horizontal wheel/trackpad input instead of preventing it; nested horizontal content can consume the gesture while any outer movement is re-aligned after it settles
  • remove the unapproved design.md additions from this PR
  • add a narrowly gated Arrow Up / Arrow Down fallback for collapsed carets at CSS-column textblock boundaries; normal navigation, modified selections, IME composition, and continuous mode remain native
  • reveal fallback caret moves through the paged controller instead of ProseMirror's generic ancestor scrolling, which could jump several spreads past the adjacent column

Verification

  • npx tsc --noEmit -p tsconfig.main.json
  • npx tsc --noEmit -p tsconfig.preload.json
  • npx tsc --noEmit -p tsconfig.renderer.json
  • npm run check:theme-colors
  • npm run build
  • 3440x1300 isolated Chromium integration pass covering two/three-page geometry, arbitrary-scroll re-snap, whole-spread vertical wheel navigation, released horizontal wheel input, document-anchor positioning, and nested horizontal scrolling
  • boundary-caret regression pass: Arrow Down moved from the first-column bottom to the second-column top, Arrow Up returned to the first column, and both moves kept the same visible spread (scrollLeft stayed aligned)
  • cross-column selection produced finite, positive selection rectangles and rendered continuously across columns
  • Chromium IME composition path committed Chinese text without losing or duplicating characters
  • table verification with 5- and 6-column long tables, including a 49 KB real-world document containing five tables; cells stayed inside page bounds and long tables continued only between rows

The packaged-app manual evidence requested in the review (macOS Chinese IME, cross-column selection, and cross-column caret movement) will be appended before requesting re-review.

An initial packaged-app caret pass exposed the overscrolling issue above; it is fixed in 838818c and now has an explicit scroll-position regression assertion. The post-fix packaged-app manual pass remains pending and is not being claimed as complete here.

This PR contains no product-name, bundle-ID, packaging, updater, or local-path changes.

@orange2ai orange2ai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个 PR 的工程质量整体不错:默认 continuous 模式零侵入我已复验(所有新路径 gated 在 paged 状态,continuous 下连 preventDefault 都不触发)、进度跨 source-mode 保留、打印导出隔离、prefers-reduced-motion 都考虑了,类型检查与构建通过。双页/三页阅读对超宽屏是真实场景。

但合并前有一个正确性缺陷和两个必须明确的问题:

1(必须):多栏模式没有任何滚动 re-snap 机制

这是分页模式下必然触发的缺陷,不是边缘 case:

  • 文档内锚点跳转 [editor.ts:390] 直接 scrollIntoView,会把内层横向 scroller 滚到标题所在列左缘——落在两个 spread 之间且无 re-snap,视图永久错位半页。同类入口大纲点击([main.ts:292])和搜索([search-panel.ts:190])都适配了,唯独锚点链接漏了。
  • 没有任何 scroll listener 对内层 scroller 做对齐校正。ProseMirror 自身的 scrollIntoView(undo/redo、文末输入、[mermaid-view.ts:105] 的 tr.scrollIntoView())和拖拽选区越出视口的原生自动滚动,都会把视图停在未对齐位置。

请补一个统一的 re-snap 机制(scroll 事件里校正到最近 spread),并给锚点跳转走 revealElement

2(必须):design.md 的新增条款未被批准

[design.md:47-50] 向规范追加了 4 条条款,其中「不做逐列自由滚动」实际上把缺陷写成了规范——多栏模式下所有 wheel 事件被 preventDefault([page-layout.ts:230-249]),包括 deltaX,导致宽代码块/宽表格连横向滚动都做不到,只能靠方向键。请:

  • 从本 PR 移除 design.md 改动(规范变更需维护者在 issue 里逐条确认,不走 PR 自带);
  • wheel 处理放行 deltaX,允许横向滚动被限宽的内容。

3(必须):补 IME 与跨列选区的实测记录

CSS multicol + contenteditable 是 Chromium 已知脆弱区:跨列选区 rects 可能为空/反转,drawSelection/coordsAtPos 视觉可能错位,跨列上下键依赖 endOfTextblock 启发式。请在 PR 描述附上中文 IME 输入、跨列选区、跨列光标移动三项的手工测试结果,附录屏或截图。

4(建议,不阻塞)

  • 文件热更新(核心卖点)在多栏下列重排没有滚动位置保持,外部 Agent 改文件后读者位置直接失效,后续至少做列级 clamp。
  • currentPageLayout 主进程单例导致多窗口菜单勾选态互相串——继承自现有 theme 的同一模式,不算本次新增问题,但若顺手可一并修。

处理完 1、2、3 即可合并。

@orange2ai

Copy link
Copy Markdown
Collaborator

复核更新:三项必改中两项已确认落实,质量很好——

  1. re-snap 机制:scroller 级 scroll 监听(180ms idle debounce)→ snapAfterScroll() 对齐最近 spread,且保留了尾部不满一页的最后一个 spread(upper = Math.min(max, lower + spreadStep))。挂在 scroller 上这一层,锚点跳转、PM scrollIntoView(undo/redo、mermaid 渲染)、拖拽选区自动滚动全部覆盖,正是我们要的统一机制;epsilon 守卫避免了无限 snap 循环。锚点已改走 revealElementInPagedLayout(editor.ts:439-441,带 HTMLElement 守卫),切回 continuous 时 cancelPendingSnap() 干净。
  2. design.md 自改已移除(067c299 回退了 4 条未批准条款);onWheel 对横向主导手势(|deltaX| > |deltaY|)不再 preventDefault,宽代码块/表格可横滚。
  3. 打包后手工测试记录仍缺:PR 描述里作者已承诺 "will be appended before requesting re-review",这是最后一项。请在 PR 描述追加:① macOS 中文 IME 输入(多栏模式下);② 跨列选区;③ 跨列光标移动(上下键跨列)三项的实测记录或录屏。

另外两个 caret 修复(ae0939b、838818c)实现正确:窄门控(仅 paged、isComposing、修饰键、空 TextSelection、textblock 边缘 + 1.5×行高的列缘启发式),Selection.findFrom(..., textOnly=true) 用法稳妥,838818c 用 dispatch + revealInPagedLayout 替代 tr.scrollIntoView() 避免多栏祖先滚动,continuous 模式不受影响。类型检查与构建均通过。

补上第 3 项即可合并。

@orange2ai

Copy link
Copy Markdown
Collaborator

维护者决定不做这个功能,关闭本 PR。

方向本身实现质量很高——尤其后续几轮补的滚动 re-snap、跨栏光标移动、caret 定位都是扎实的工作,这点没有争议。但产品层面判定它超出 ColaMD 的边界:

  • ColaMD 的界面只有标题栏、编辑器、文件面板三个区域,产品定位是编辑而不是分页阅读;超宽屏双页/三页是阅读器场景
  • 代价不对称:480 行分页逻辑 + 滚轮/触控板/翻页键的手势接管 + CSS multicol 与 contenteditable 的已知脆弱区(中文 IME、跨列选区、热更新重排后位置失效),而收益只覆盖超宽屏一个边缘场景
  • 这与「如非必要,勿增实体」「一个功能做到极致」的取向相反

决定已记录在 docs/feature-requests.md 的 Declined 区,避免以后再重复讨论。感谢你认真按 review 意见补齐实现细节——这份工程投入我们都看到了,只是功能本身不进入产品。

@orange2ai orange2ai closed this Sep 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants