Skip to content

Commit f1fa18a

Browse files
committed
docs(grace): C-22 spec + detailed plan — Split View (last open P1), corner matrix C-1..C-10, four-corner review protocol
1 parent c1ee678 commit f1fa18a

2 files changed

Lines changed: 239 additions & 0 deletions

File tree

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
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 &amp;&amp; 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 &gt; 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 &amp; 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 &amp; 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 &amp; 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 &amp; 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 &lt; 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>
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<GraceChangeSpec graceVersion='4.0' status='approved'>
2+
<C-22>
3+
<Name>Split View: WYSIWYG and source side-by-side (matrix P1, last open P1)</Name>
4+
<Created>2026-09-29</Created>
5+
<Priority>medium</Priority>
6+
<Status>approved-for-implementation</Status>
7+
8+
<Summary>
9+
The feature matrix (docs/research/markdown-feature-matrix.md) lists
10+
"Split view (edit + rendered side-by-side)" as the last open P1 gap:
11+
VS Code and Antigravity show preview beside source; Mark today is an
12+
either-mode toggle. Deliver a split layout: active editor mode on one
13+
pane, the other mode mirrored read-only on the second pane, live
14+
synced on changes (debounced), with pane focus deciding which surface
15+
is editable. Menu item View > Split View (Cmd+\), layout state
16+
persisted per window, split only rendered for the current tab (not
17+
global).
18+
</Summary>
19+
20+
<Problem>
21+
<Description>Users switching modes lose the "how it renders" or "how it reads as source" context; parity gap vs code editors.</Description>
22+
<RootCause>editorWithTabs renders exactly one of editor.vue|sourceCode.vue per mode flag.</RootCause>
23+
<Evidence>Feature matrix P1 row; VS Code docs (source+preview side-by-side).</Evidence>
24+
</Problem>
25+
26+
<Goals>
27+
<R-1>Split layout component hosting both existing mode components; no new editor internals.</R-1>
28+
<R-2>Read-only mirror pane syncs from the editable pane content (debounce 150ms), never writes back.</R-2>
29+
<R-3>View menu item + Cmd+\ toggle; persistence of split state and ratio (draggable divider) per window.</R-4>
30+
<R-4>Both themes; both directions (source-left and rich-left) via double-click divider swap.</R-4>
31+
</Goals>
32+
33+
<Constraints>
34+
<Constraint>Zero new runtime dependencies.</Constraint>
35+
<Constraint>Editor internals (muya, CodeMirror) stay untouched; split is a container concern.</Constraint>
36+
<Constraint>Per-tab: switching tabs keeps each tab's own split state; split off by default.</Constraint>
37+
<Constraint>Footprint: split must not double memory-holding — mirror pane renders from the same markdown string, no second document model beyond what each surface already builds.</Constraint>
38+
</Constraints>
39+
40+
<NonGoals>
41+
<NonGoal>Simultaneous editing in both panes (mirror is read-only).</NonGoal>
42+
<NonGoal>Vertical (stacked) split; horizontal only for v1.</NonGoal>
43+
<NonGoal>Split in diff view or settings windows.</NonGoal>
44+
</NonGoals>
45+
46+
<AcceptanceCriteria>
47+
<Criterion>Cmd+\ toggles split for the current tab; both panes render the same document; typing in the editable pane updates the mirror within 300ms on a 10k-line doc.</Criterion>
48+
<Criterion>Mode toggle while split swaps which pane is editable without losing content or cursor in the editable pane.</Criterion>
49+
<Criterion>Split state + divider ratio survive app restart per window; fresh tabs default to split-off.</Criterion>
50+
<Criterion>Existing renderer suites green; new split tests green; visual probe screenshots (light+dark) attached to the plan ReviewLog.</Criterion>
51+
</AcceptanceCriteria>
52+
53+
<AffectedAreas>
54+
<M-011>titleBar untouched; editorWithTabs container</M-011>
55+
<New>components/editorWithTabs/splitView.vue</New>
56+
</AffectedAreas>
57+
58+
<VerificationIntent>
59+
Unit: container logic (state per tab, ratio persistence). E2E:
60+
tauri-shim boot, split toggle, mirror-sync assertions on content
61+
change, divider persistence across reload. Corner matrix below in
62+
plan. Review: four-corner protocol (Correctness, Security/Privacy,
63+
UX/Docs, Performance) as in C-13/C-15.
64+
</VerificationIntent>
65+
66+
<Approval>
67+
<ApprovedBy>user (session 2026-09-29): "давай детальный грейс план с верификацией корнерами и ревью по 4 углам как всегда"</ApprovedBy>
68+
<ApprovalScope>Spec + detailed plan now; implementation after C-16/C-17 land (queue order per user).</ApprovalScope>
69+
</Approval>
70+
</C-22>
71+
</GraceChangeSpec>

0 commit comments

Comments
 (0)