|
| 1 | +# q2-preview scroll sync (bd-9kzfi) |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +Scroll sync (editor ↔ preview) works for the HTML preview (`MorphIframe`) |
| 6 | +but is a no-op for the q2-preview format (`ReactPreview` → `Q2PreviewIframe`). |
| 7 | +Two gaps: |
| 8 | + |
| 9 | +1. **No source-line attributes in the q2-preview DOM.** `MorphIframe` maps |
| 10 | + editor line ↔ preview position via `data-loc="fileId:startLine:startCol-endLine:endCol"` |
| 11 | + attributes the Rust HTML writer stamps on each element. The q2-preview React |
| 12 | + renderer stamps only `data-sid` (and only under attribution). `findElementForLine` |
| 13 | + has nothing to match. |
| 14 | +2. **No scroll plumbing on the React path.** `ReactPreview` never calls |
| 15 | + `useScrollSync`; `Q2PreviewIframe` exposes no `scrollToLine`/`getScrollRatio` |
| 16 | + handle and attaches no `scroll`/`click` listeners; `ReactRenderer` threads none. |
| 17 | + |
| 18 | +Per-node line numbers already ride the wire: `renderPageInProject` emits |
| 19 | +`include_inline_locations: true`, so every node carries an `l` field |
| 20 | +(`{f, b:{o,l,c}, e:{o,l,c}}`). The q2-preview iframe is `allow-same-origin`, |
| 21 | +so the parent can reach `contentDocument` directly and reuse `MorphIframe`'s |
| 22 | +exact scroll logic. **No Rust change, no new postMessage protocol.** |
| 23 | + |
| 24 | +Scope (confirmed with user): **block-level scroll sync only.** No inline |
| 25 | +granularity, no selection mirroring. |
| 26 | + |
| 27 | +## Design |
| 28 | + |
| 29 | +- `data-loc` must land on each leaf's **own** semantic element (no wrapper): |
| 30 | + wrappers (even `display:contents` / `position:relative`) break theme CSS |
| 31 | + child/adjacency/`:nth-child` selectors and margin collapse — a parity risk |
| 32 | + this repo guards hard. |
| 33 | +- New helper `dataLocProps(node)` (framework) → `{ 'data-loc'?: string }`, |
| 34 | + spread onto each q2-preview block leaf's root element. |
| 35 | +- Extract `parseDataLoc` / `findElementForLine` / `isElementVisible` into a |
| 36 | + shared `iframe/scrollSyncDom.ts`; `MorphIframe` and `Q2PreviewIframe` both |
| 37 | + import them. |
| 38 | +- `Q2PreviewIframe` gains a `ref` handle (`scrollToLine`, `getScrollRatio`) |
| 39 | + + `onScroll`/`onClick` props, using direct same-origin `contentDocument` |
| 40 | + access (mirrors `MorphIframe`). |
| 41 | +- `ReactRenderer` forwards the handle ref + callbacks to `Q2PreviewIframe` |
| 42 | + (q2-preview branch only). |
| 43 | +- `ReactPreview` wires `useScrollSync` exactly like `Preview.tsx`. |
| 44 | + |
| 45 | +## Work Items |
| 46 | + |
| 47 | +### Phase 1 — data-loc emission (TDD) |
| 48 | +- [x] Test: `dataLocProps` returns correct string for node with `l`, `{}` without |
| 49 | +- [x] Test: Para/Header/CodeBlock/Div with `l` render `data-loc`; node without `l` has none |
| 50 | +- [x] Implement `framework/sourceLoc.ts` + export |
| 51 | +- [x] Spread `dataLocProps(node)` into block leaves: Para, Header, CodeBlock |
| 52 | + (both paths), BulletList, OrderedList, BlockQuote, Div, HorizontalRule, |
| 53 | + RawBlock, Figure, LineBlock, DefinitionList, Table (skipped Plain — Fragment) |
| 54 | + |
| 55 | +### Phase 2 — scroll plumbing (TDD where feasible) |
| 56 | +- [x] Test: `findElementForLine` / `parseDataLoc` in shared module (jsdom) |
| 57 | +- [x] Extract `iframe/scrollSyncDom.ts`; refactor `MorphIframe` to import |
| 58 | +- [x] `Q2PreviewIframe`: ref handle + scroll/click listeners (same-origin) |
| 59 | +- [x] `ReactRenderer`: thread ref + onScroll/onClick to Q2PreviewIframe |
| 60 | +- [x] `ReactPreview`: wire `useScrollSync` |
| 61 | + |
| 62 | +### Phase 3 — verify |
| 63 | +- [x] `npm run build` (production tsc -b + vite) green; WASM untouched (TS-only) |
| 64 | +- [x] vitest suites green (preview-renderer 186+192; hub-client 556 unit + 66 integ) |
| 65 | +- [x] Added pampa regression test proving real parser output carries `l` on |
| 66 | + *block* nodes (`json_location_test::test_json_location_on_block_nodes`), |
| 67 | + and jsdom tests proving the real `<Ast>` render stamps `data-loc` from it. |
| 68 | + **NOT verified:** live browser scroll interaction (same-origin iframe DOM |
| 69 | + access + Monaco cursor events) — needs a running hub + browser session. |
| 70 | +- [ ] changelog.md entry (two-commit workflow) — pending commit |
| 71 | +- [ ] beads bd-9kzfi close + sync — pending commit |
0 commit comments