feat(editor): M3 — the bold and italic toggles #41
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/manuscript-toggles"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
M3 of the manuscript editor track (ADR 0030,
PROJECT_PLAN.md): Ctrl+B / Ctrl+I as their own undo step, pasted formatting normalised, and a formatted paragraph in the seed. A separate commit hardens M1's codec timing check, following Jibril's note on #38.What changes
The toggles.
toggleMarkcommands.format*input stays cancelled.FORMATmeta):Reshapewith the new reasonformatcovers every paragraph the toggle changed, and it is journaled as formatted text.Paste keeps
<b>/<strong>and<i>/<em>, and now also reads bold and italic set by a style, as Google Docs, Word and a browser copying a page write them:bold, reads as bold, andfont-style: italicas italic;<b style="font-weight:normal">, and a page's CSS reset lands on the copied tag.Every other mark is still dropped.
Server.
ReshapeReason.Formatis mapped from the wire'sformatand labelled formatted text. Its provenance rule is the reshape's: rewritten run text is markededited.Seed. One paragraph in "What the Pact Said" is made italic, then bold, through two
formatreshapes, the way the toggles send them. History shows both steps.Codec timing check (separate commit,
a522636). Jibril reproduced a false failure under CPU contention: 46.8× against the 32× limit. Each workload now does the same work two ways — eight texts of one size against one text eight times as long — timed against each other in five alternating rounds, fastest of each. Linear measures 0.6–2.0 in every condition tried: quiet, under coverage, with two spinners on four cores, and with every core busy. The limit is 4, and a quadratic join still fails it.Docs.
formatreason and label, the bare-caret toggle, and the paste rules.MANUAL-MATRIX.md:Verification
format.test.ts: toggles across paragraphs after pending typing, the stamps, turning a mark off, italic and nesting, a bare caret, a scene break in the selection, and the pending mark kept through a delta and through a split's late answer);formatBoldinput is cancelled;Open
🤖 Generated with Claude Code
Summary
Summary
Coverage
Kagaku.Flow - 99.7%
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__ColorToken_2
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__Socket_1
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__TypeId_0
Kagaku.Flow.OpenRouter - 100%
Kagaku.Flow.Testing - 100%
Tsumugi.BlazorAdapter - 95.6%
Tsumugi.Domain - 96.1%
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__DigitsAndSeparators_2
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__LineBreak_0
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__NumberToken_1
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__Plain_4
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__ThousandsGroup_3
Tsumugi.Infrastructure - 97.9%
A55E0373ED22DC58420DBB40F103A46B68DC4D9CE5A49E00B__Pattern_0
on
tion
n
Tsumugi.Kernel - 90.9%
Tsumugi.Server - 100%
Tsumugi.UseCases - 98.3%
🔮 fufu~ Jibril reviewed your code!
Oh? Oh! Toggles that arrive as their own journaled step, a stored mark that refuses to die just because another tab whispered~ — this is the kind of PR I want to pick up and hug. And then probe with nine scalpels to see if it screams. It didn't. ♡
Verdict: ✅ Looks good to me~
Every number in the PR body reproduced exactly in my clone, and every load-bearing line survived a mutation probe on its own pin. No blockers. Two little ideas below — neither holds this PR.
✅ What I liked~
FORMATmeta routing (session.ts) is elegant: derive still classifies the toggle astyping(it only changes texts), andchanged()reroutes it to onereshape(reason 'format')aftersendTyping(before)flushes the pending pause as it stood. Pending-typing-first, one Reshape over every touched paragraph, stamps read at send time after the typing's answer — the ordering contract is exactly ADR 0030's, and test 2 pins the stamp chain the server'sSameStampcheck demands.keepStoredMarksis the subtle jewel. ProseMirror drops stored marks on any remote step; re-arming them only when the caret's (id, offset) survived — and dropping them when it moved — splits the world precisely between "another tab's doing" and "the writer changed context". Tests pin both arms (delta keeps, moved-caret drops, split's late answer keeps).schema.ts): clearing rule ordered before the setting rule, tag-own-normal checked on<b>/<strong>/<i>/<em>alike (the Google Docs<b style="font-weight:normal">wrapper and the CSS-reset<b style="font-weight:400">both handled), threshold at 600 with 500 and 1000 pinned on the correct sides, underline/colour dropped. The e2e fixtures read like field notes from real clipboards. ♪ReshapeSegmentspath the editor sends — no shortcut — and the new integration test pins the journal to[inserted a paragraph, formatted text, formatted text]via raw SQLite. The way it should be.FromRuns(string-concat instead of the builder): all four workload rows went red. And I hit my own stale-binary trap probing it — rebuilt properly, still red. The check has teeth."format" => ReshapeReason.Format), label ("formatted text"),InlineDatarow, enum-count 7→8. Sibling-exact.My probes, each red on its own pin, each restored pristine: format-read-as-typing (5 red), lost reason (2),
keepStoredMarksneutered (2), caret-moved guard broken (exactly 1), keymap unbound (1), threshold 600→700 (1), tag-own-normal ignored for bold (1), italic clearing rule dropped (1), quadratic join (4/4). One probe of mine initially ran against an unsubstituted file and another against a stale binary — both redone before believing anything, per my own rules. fufu~ I don't pardon myself either.Local verification: vitest 177/177 (168 + exactly 9 new), typecheck clean, vite build 231.39 kB / 72.47 kB gzip; dotnet Release 0w/0e and the full matrix green digit-for-digit (Domain 469, UseCases 155, Integration 374, BlazorAdapter 356, Flow 1103, Flow.OR 592+4 skipped); Playwright Chromium 36/36 including all five new M3 tests. WebKit/Firefox can't launch in my sandbox (missing gstreamer libs — environmental, not yours); the three-engine run is CI's editor-browsers job, and the coverage bot's report at this exact head shows the C# numbers above.
The Open item (empty line under a paragraph showing no placeholder) is honestly disclosed and correctly diagnosed as Shift+Enter's line belonging to the paragraph above — waiting on bjoern's steps is the right call, not this PR's scope.
💡 Little ideas (non-blocking)~
heavy()— the amendment names "a weight of 600 or more,bold" butschema.ts:13also readsbolder. One word in the ADR would make doc and code say the same thing.format.test.ts— the one remote path without its own pin for the pending mark is a refusal's adoption (reconcileAdoption is REMOTE-meta too, sokeepStoredMarksruns there). The arm is shared with the delta path, so nothing hides — but a ~10-line pin would close the set completely.Fufu~ M3 in three clean commits, the #38 debt paid in full, and a Record row for the owner's staging pass. The manuscript editor is becoming something quite lovely~ ♡
Automated review by Jibril · 2026-09-26
CI/CD: passed for head SHA
5b2d9b2(coverage bot #9872, current) · Local checks: vitest 177/177 · Chromium e2e 36/36 · dotnet 6-project matrix green · 9 mutation probes killed