|
| 1 | +<GraceChangePlan graceVersion='4.0' status='approved'> |
| 2 | +<C-22> |
| 3 | + <ChangeRef>C-22</ChangeRef> |
| 4 | + <Created>2026-09-29</Created> |
| 5 | + <IntentSummary>Split View container: mirror one mode beside the other, read-only sync, divider ratio persisted, Cmd+\ per-tab toggle. Four-corner review protocol; corner matrix drives both tests and review scope.</IntentSummary> |
| 6 | + <BaselineAssertions> |
| 7 | + <MustContain> |
| 8 | + <File>src/renderer/src/components/editorWithTabs/index.vue</File> |
| 9 | + <Text>sourceCodeModeEnabled</Text> |
| 10 | + </MustContain> |
| 11 | + <MustContain> |
| 12 | + <File>src/renderer/src/store/preferences.js</File> |
| 13 | + <Text>sourceCodeModeEnabled</Text> |
| 14 | + </MustContain> |
| 15 | + </BaselineAssertions> |
| 16 | + <TargetAssertions> |
| 17 | + <MustContain> |
| 18 | + <File>src/renderer/src/components/editorWithTabs/splitView.vue</File> |
| 19 | + <Text>mirror</Text> |
| 20 | + </MustContain> |
| 21 | + <MustContain> |
| 22 | + <File>src/renderer/src/commands/index.js</File> |
| 23 | + <Text>view.split-view</Text> |
| 24 | + </MustContain> |
| 25 | + <MustContain> |
| 26 | + <File>tests/renderer/components/split-view.test.js</File> |
| 27 | + <Text>mirror</Text> |
| 28 | + </MustContain> |
| 29 | + <MustContain> |
| 30 | + <File>tests/e2e/split-view.spec.ts</File> |
| 31 | + <Text>split</Text> |
| 32 | + </MustContain> |
| 33 | + </TargetAssertions> |
| 34 | + <DurableScope> |
| 35 | + <New>splitView.vue container (no kernel changes)</New> |
| 36 | + </DurableScope> |
| 37 | + <ObservedWriteScope> |
| 38 | + <File>src/renderer/src/components/editorWithTabs/splitView.vue (new)</File> |
| 39 | + <File>src/renderer/src/components/editorWithTabs/index.vue (mount split)</File> |
| 40 | + <File>src/renderer/src/store/preferences.js (splitEnabled, splitRatio persistence)</File> |
| 41 | + <File>src/renderer/src/commands/index.js + descriptions.js (view.split-view, Cmd+\)</File> |
| 42 | + <File>src/renderer/src/i18n/locales/* (split strings)</File> |
| 43 | + <File>tests/renderer/components/split-view.test.js (new)</File> |
| 44 | + <File>tests/e2e/split-view.spec.ts (new)</File> |
| 45 | + <File>.grace/graph/features.xml + index (module entry)</File> |
| 46 | + <File>.grace/verification/features.xml + index (V-M-0xx)</File> |
| 47 | + </ObservedWriteScope> |
| 48 | + <ImplementationPlan> |
| 49 | + <T-221> |
| 50 | + <Title>Container + state (per-tab split, ratio persistence)</Title> |
| 51 | + <DependsOn></DependsOn> |
| 52 | + <AcceptanceCriteria> |
| 53 | + <Criterion>splitView.vue renders both mode components; editable side follows current mode; mirror is a second instance flagged read-only via existing component props/patching the minimum; divider drag updates ratio; ratio + enabled saved to prefs (per-window file as today's window-state).</Criterion> |
| 54 | + </AcceptanceCriteria> |
| 55 | + <Verification> |
| 56 | + <Command>npx vitest run tests/renderer/components/split-view.test.js</Command> |
| 57 | + </Verification> |
| 58 | + </T-221> |
| 59 | + <T-222> |
| 60 | + <Title>Sync engine: debounced mirror update</Title> |
| 61 | + <DependsOn>T-221</DependsOn> |
| 62 | + <AcceptanceCriteria> |
| 63 | + <Criterion>Single source of truth: the active pane's markdown string; mirror re-renders on change with 150ms debounce; mirror NEVER writes to store; typing latency in editable pane unchanged (no sync work on keystroke path beyond a dirty flag).</Criterion> |
| 64 | + </AcceptanceCriteria> |
| 65 | + <Verification> |
| 66 | + <Command>npx vitest run tests/renderer/components/split-view.test.js -t sync</Command> |
| 67 | + </Verification> |
| 68 | + </T-222> |
| 69 | + <T-223> |
| 70 | + <Title>Menu command + accelerator + i18n</Title> |
| 71 | + <DependsOn>T-221</DependsOn> |
| 72 | + <AcceptanceCriteria> |
| 73 | + <Criterion>view.split-view command toggles for CURRENT tab; Cmd+\ bound via existing keybinding registry; description strings en/ru/…; command palette picks it up automatically (registry).</Criterion> |
| 74 | + </AcceptanceCriteria> |
| 75 | + <Verification> |
| 76 | + <Command>npx vitest run tests/renderer/commands/menu-consistency.test.js</Command> |
| 77 | + </Verification> |
| 78 | + </T-223> |
| 79 | + <T-224> |
| 80 | + <Title>E2E + visual probes + governance</Title> |
| 81 | + <DependsOn>T-222, T-223</DependsOn> |
| 82 | + <AcceptanceCriteria> |
| 83 | + <Criterion>Playwright: boot → toggle split → assert two panes → edit source → mirror content changes within 300ms → reload → split+ratio restored. Probes: light/dark screenshots into plan ReviewLog. grace lint 0; graph/verification entries registered.</Criterion> |
| 84 | + </AcceptanceCriteria> |
| 85 | + <Verification> |
| 86 | + <Command>npx playwright test tests/e2e/split-view.spec.ts && grace lint --path . --assertions current</Command> |
| 87 | + </Verification> |
| 88 | + </T-224> |
| 89 | + </ImplementationPlan> |
| 90 | + <CornerMatrix> |
| 91 | + <!-- Corners drive tests AND the four reviewers' checklists. --> |
| 92 | + <Corner id="C-1"> |
| 93 | + <Situation>Toggle split while a document has unsaved changes / dirty dot</Situation> |
| 94 | + <Expected>content preserved bit-identical; dirty state untouched; no autosave trigger</Expected> |
| 95 | + </Corner> |
| 96 | + <Corner id="C-2"> |
| 97 | + <Situation>Split ON, then mode toggle (Cmd+Alt+S) in editable pane</Situation> |
| 98 | + <Expected>panes swap editability; editable cursor position preserved per its own surface; no focus theft</Expected> |
| 99 | + </Corner> |
| 100 | + <Corner id="C-3"> |
| 101 | + <Situation>Tab switch: tab A split-on ratio 30%, tab B split-off</Situation> |
| 102 | + <Expected>each tab restores its own state on return; no cross-tab leakage</Expected> |
| 103 | + </Corner> |
| 104 | + <Corner id="C-4"> |
| 105 | + <Situation>10k-line document, rapid typing (30 chars burst)</Situation> |
| 106 | + <Expected>mirror updates coalesce (≤2 re-renders per burst); editable pane keystroke latency unchanged (no main-thread stall > 16ms)</Expected> |
| 107 | + </Corner> |
| 108 | + <Corner id="C-5"> |
| 109 | + <Situation>Live-reload external edit while split (watcher fires)</Situation> |
| 110 | + <Expected>both panes update per existing reload semantics; cursor-preservation logic applies to the EDITABLE pane only</Expected> |
| 111 | + </Corner> |
| 112 | + <Corner id="C-6"> |
| 113 | + <Situation>Window resized below 800px width</Situation> |
| 114 | + <Expected>split collapses to single-pane (mode toggle remains); re-expanding restores ratio; no clipped half-panes</Expected> |
| 115 | + </Corner> |
| 116 | + <Corner id="C-7"> |
| 117 | + <Situation>App restart with split-on</Situation> |
| 118 | + <Expected>split+ratio restored via prefs; failure to read prefs defaults split-off (fail-open)</Expected> |
| 119 | + </Corner> |
| 120 | + <Corner id="C-8"> |
| 121 | + <Situation>Print/export/screenshot commands while split</Situation> |
| 122 | + <Expected>operate on the document as today (single-model); no pane duplication in output</Expected> |
| 123 | + </Corner> |
| 124 | + <Corner id="C-9"> |
| 125 | + <Situation>Split + WYSIWYG math/mermaid heavy doc</Situation> |
| 126 | + <Expected>mirror defers heavy renders (existing lazy semantics); no double network/local resource fetch</Expected> |
| 127 | + </Corner> |
| 128 | + <Corner id="C-10"> |
| 129 | + <Situation>Divider dragged to 0%/100% extremes</Situation> |
| 130 | + <Expected>clamped at 10/90; no zero-width pane or divider loss</Expected> |
| 131 | + </Corner> |
| 132 | + </CornerMatrix> |
| 133 | + <FourCornerReview> |
| 134 | + <!-- "Как всегда": four independent review passes, each with its own |
| 135 | + checklist derived from the corners; verdicts land in ReviewLog. --> |
| 136 | + <ReviewPass number="1" name="Correctness & Contracts"> |
| 137 | + <Checks> |
| 138 | + <Check>MODULE_CONTRACT/MAP present and true for splitView.vue</Check> |
| 139 | + <Check>Corners C-1..C-3, C-7 behaviour matches Expected verbatim</Check> |
| 140 | + <Check>no writes from mirror; single document model (assert store usage)</Check> |
| 141 | + </Checks> |
| 142 | + </ReviewPass> |
| 143 | + <ReviewPass number="2" name="Security & Privacy"> |
| 144 | + <Checks> |
| 145 | + <Check>no new IPC, no new permissions/capabilities, no network</Check> |
| 146 | + <Check>mirror renders through the same sanitization paths (no raw-html bypass)</Check> |
| 147 | + <Check>public-repo hygiene: no secrets, no signing material near the change</Check> |
| 148 | + </Checks> |
| 149 | + </ReviewPass> |
| 150 | + <ReviewPass number="3" name="UX & Docs"> |
| 151 | + <Checks> |
| 152 | + <Check>Corners C-2, C-6, C-10 feel right (no focus theft, no lost divider)</Check> |
| 153 | + <Check>guide page updated (site/docs) with Cmd+\ and corner behaviors</Check> |
| 154 | + <Check>i18n strings complete; menu/palette consistency test updated</Check> |
| 155 | + </Checks> |
| 156 | + </ReviewPass> |
| 157 | + <ReviewPass number="4" name="Performance & Footprint"> |
| 158 | + <Checks> |
| 159 | + <Check>Corners C-4, C-9: burst coalescing proven; keystroke path unchanged</Check> |
| 160 | + <Check>RAM delta with split-on idle < 25MB on the 10k-line fixture</Check> |
| 161 | + <Check>no new dependencies; bundle delta ≤ 3KB gz</Check> |
| 162 | + </Checks> |
| 163 | + </ReviewPass> |
| 164 | + </FourCornerReview> |
| 165 | + <ReviewLog> |
| 166 | + </ReviewLog> |
| 167 | +</C-22> |
| 168 | +</GraceChangePlan> |
0 commit comments