feat(core): scroll-synced split preview via sourceMap export (ROADMAP #13)#116
feat(core): scroll-synced split preview via sourceMap export (ROADMAP #13)#116zaizailxm wants to merge 2 commits into
Conversation
…loatboatai#13) Core: - exportHTML({ sourceMap: true }) stamps blocks with data-nexus-from/to source offsets (rehypeSourceMap); descends into list/blockquote containers so outline-style documents get per-item anchors. Default exportHTML() output is byte-identical to before. - EditorAPI: "scroll" event, getScrollAnchor() / scrollToAnchor(offset) with sub-line interpolation over CM6 line blocks. - createScrollSync(): bidirectional editor<->preview sync. Linear interpolation between source-mapped anchors keeps unevenly rendered content (code fences, tables) aligned; a driver lock with a 150ms window suppresses programmatic-scroll feedback loops; rAF-coalesced handlers; hidden previews are skipped entirely. - themeToCssVars() exported so host UI outside the editor DOM can apply the same theme variables instead of hand-copying names. Electron demo: - Split preview pane (toolbar toggle + persisted setting), rendered via exportHTML sourceMap and wired through createScrollSync. Re-rendered on debounced change events and explicitly after silent file loads, which bypass the change pipeline. Tests: 38 new (mapping math incl. inverse round-trips, source-map granularity, feedback-loop suppression, hidden-pane guard, preview pane lifecycle). Verified end-to-end in the packaged Electron app: typing, bidirectional wheel scroll, live update, toggle. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…export Full-range monotonicity sweeps for both mapping directions (no NaN, no regressions across anchor gaps), negative scrollTop (elastic overscroll), zero-height anchors, and empty-document behavior for exportHTML sourceMap and the scroll-anchor APIs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
redreamality
left a comment
There was a problem hiding this comment.
Reviewed PR #116.
This is a substantial implementation of ROADMAP #13: scroll-synced split preview using source-offset anchors rather than naive percentage syncing. The scope matches the roadmap entry, and the PR correctly documents that OpenSpec is not required for this item.
Validation performed in an isolated checkout:
pnpm exec tsc --noEmit -p packages/core/tsconfig.json
pnpm exec tsc --noEmit -p apps/electron-demo/tsconfig.json
pnpm exec tsc --noEmit -p packages/react/tsconfig.json
pnpm exec tsc --noEmit -p packages/vue/tsconfig.json
pnpm --filter @floatboat/nexus-core test -- scroll-sync export-html-source-map --reporter=verbose
pnpm --filter @floatboat/nexus-core test -- --runInBand
These checks passed.
Review notes:
- CLA is passing.
exportHTML({ sourceMap: true })preserves the default non-source-map behavior while stamping rendered blocks with source offsets for sync consumers.createScrollSync()has sensible bidirectional mapping primitives, rAF-coalescing, cached anchor invalidation, hidden-preview short-circuiting, and teardown of editor/DOM/window listeners.- The demo integration keeps the preview hidden by default, re-renders only when needed, refreshes after silent file loads, applies shared theme CSS variables, and destroys listeners cleanly.
- Tests cover source-map generation, mapping edge cases, editor scroll APIs, and preview pane lifecycle behavior.
One local environment note: pnpm --filter @floatboat/nexus-electron-demo test -- preview-pane --reporter=verbose could not start because Rollup's macOS optional native package failed to load under this local environment/code-signature setup. This is the same Rollup optional-dependency/native loading issue seen in prior reviews and is not evidence of a PR code failure; the electron-demo TypeScript check passed, and the preview-pane test source itself was reviewed.
Conclusion: approved from code-review perspective.
Summary / 摘要
Scroll-synced split preview (ROADMAP #13):
exportHTML({ sourceMap: true })stamps blocks with source offsets, newEditorAPIscroll-anchor APIs, acreateScrollSync()bidirectional sync engine in core, and a split-preview reference integration in the electron demo.Motivation / 背景与动机
core, P2, marked "Needs OpenSpec: No")Inline live preview hides the rendered form of the exact block the cursor is in, and users who edit in pure source mode have no rendered view at all. A split preview needs scroll sync to be usable — naive percentage sync drifts badly when content renders at a different height than its source (code fences, tables). This PR implements offset-anchored sync as the roadmap requests.
Changes / 变更内容
packages/core:exportHTML(options?)— optional{ sourceMap: true }stamps block elements withdata-nexus-from/data-nexus-tosource offsets (newrehypeSourceMap). Descends into list/blockquote containers so outline-style documents get per-item anchors. Default output is byte-identical to before.EditorAPI: newscrollevent;getScrollAnchor()/scrollToAnchor(offset)with sub-line interpolation over CM6 line blocks.scroll-sync.ts:createScrollSync({ editor, preview })— bidirectional sync. Pure mapping functions linearly interpolate inside anchors and across gaps; a driver lock (150 ms sliding window) suppresses programmatic-scroll feedback loops; handlers are rAF-coalesced; hidden previews are skipped; anchors cache invalidates onrefresh()/ window resize.themeToCssVars(theme)exported so host UI outside the editor DOM can apply the same theme variables.apps/electron-demo:preview-pane.ts— reference integration: toolbar ◨ toggle + persisted "Split preview" setting; re-renders on debouncedchangeevents and explicitly after silent file loads (which by design bypass the change pipeline).docs/ROADMAP.md/docs/ROADMAP.zh.md: feat(slash): add priority sort and result limit to slash commands #13 → done.packages/core/README.md: new "Scroll sync & source-mapped HTML export" section.Testing / 测试
pnpm testpasses — 559 tests / 37 files (44 new)pnpm build),pnpm -r exec tsc --noEmitcleanCompliance / 合规自检
Checklist / 自检清单
live-preview-table.tsuntouchedScreenshots / Recordings · 截图或录屏 (UI changes)
(见下方附图:分屏打开、双向滚动同步对齐)


