feat(editor): punctuation set as it is typed — <<, >>, ..., --, ---, quotes in the chosen style (writing aids W1) #79
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/writing-aids-punctuation"
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?
This is slice W1 of the writing-aids track (ADR 0032, stories/writing-aids.md).
What's in it
punctuation.ts, built onprosemirror-inputrules1.5.1, pinned exactly beside the other ProseMirror packages.<<→ «,>>→ »,...→ …,--→ –, and an em dash from either---(three hyphens, e.g. after Backspace restored two) or–-(typed straight through)."and'follow the quote style (English “” ‘’, German „“ ‚‘, French «» ‹›, guillemets »« ›‹, or straight).opensAfter, is now exported fromdialogue.tsand shared.'that doesn't open speech is the apostrophe ’.punctuationmount option, the quote style's spelling or null. With the option set, Backspace runsundoInputRulefirst, so it takes back a replacement until the caret moves. That window is exactly as ADR 0032 now states it.editor.typing. The switch and the quote style live in their own setting:EditorTypingJson(read leniently, saved strictly) andGet/SaveEditorTyping;EditorTypingFluxor slice, its state, reducers and effects in separate files with counted, ordered saves;Tests
Browser suite, Chromium and Firefox:
--, then---, then–-when typed straight through; after a caret move it only deletes;The full suite: 140 passed. The 6 skips are the 5 existing ones plus the composition test in Firefox.
bUnit:
punctuationmount option for no row, German, straight and off;BlazorAdapter: 738, three full runs in a row.
EditorTypingJsonTests: the spellings, the exact rows, readback, wrong kinds, the defaults and the refusal. UseCases: 514.Every other suite passes: Domain 517, Integration 836, Kagaku.Flow 1103, OpenRouter 592 (+4).
Mutation probes, each killed:
---rule; straight style dropping the dashes; always on;The straight-style and guard probes both survived at first. Each now has the test that kills it.
Seeded app in Chromium:
"Komm" -- sagte sie... <<ja>>at the end of a chapter gives„Komm“ – sagte sie… «ja», stored that way across a reload;The pass also caught a hint that showed
"literally. It's fixed and now pinned.🤖 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 - 97%
Tsumugi.Domain - 96.5%
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__DigitsAndSeparators_2
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__LineBreak_0
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__NumberToken_1
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__Plain_4
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__ThousandsGroup_3
Tsumugi.Infrastructure - 99%
A55E0373ED22DC58420DBB40F103A46B68DC4D9CE5A49E00B__Pattern_0
on
tion
n
n
Tsumugi.Kernel - 90.9%
Tsumugi.Server - 99.6%
Tsumugi.UseCases - 98.9%
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__HexColour_1
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__LineBreaks_0
🔮 fufu~ Jibril reviewed your code!
Oh? Oh! Input rules that respect the dialogue scan's opener rule, a backspace window with a full state machine across
--→---→–-, composition tested over CDP, and a C# slice that mirrors its appearance sibling line for line… I read this one slowly, savouring it~ ♡Verdict: ⛔ I can't let this pass~ ♡
One probe survived, and you know how I feel about survivors.
⛔ These need fixing before I'm satisfied~
src/Tsumugi.BlazorAdapter/Settings/EditorTypingEffects.cs:25— the typing slice's save serialization has no test, and I can prove it. The class promises (its own doc comment): "saves run one at a time, in the order the section sent them, so the row ends as the writer's last change" — that promise is the entire point of_saves. I removed the semaphore (await _saves.WaitAsync()plus thetry/finally), rebuilt, and ranEditorTypingTests: all 9 passed, green as a spring meadow. The interleaving test you have (A_later_save_that_fails_after_an_earlier_one_landed_keeps_its_change_shown) dispatchesEditorTypingSaved/EditorTypingSaveFailedby hand — it guards the reducers' counting, not the effect's ordering.The sibling settles this:
EditorAppearanceTests.Saves_wait_for_the_one_before_them(EditorAppearanceTests.cs:225) holdsWriteGate, starts twoOnSaveAsynccalls, asserts the second waits behind the first, then asserts both land in order and the row holds the last change. Without it, a writer flipping the switch and the style quickly can end with the older row stored — a real lost-update, silent as snow. ♡Fix: the typing twin of that test — two
OnSaveAsynccalls,WriteGateheld,Assert.Empty(AppSettings.Writes)while the first is held, release, then assert the dispatchedEditorTypingSavedorder and the final row.💡 Little ideas (non-blocking)~
view.ts:147—undoPunctuationchains ahead of plainBackspaceonly;Shift-BackspaceandMod-Backspacestill run barebackspace. The ADR names plain Backspace, so this follows it — but on macOS some writers' keyboards send Shift+Backspace for the same key; worth a thought in a later slice.docs/stories/writing-aids.md:44— the style list reads "German »« ›‹" where the card says "Guillemets"; a tiny wording slip from before this PR, now that the line is ticked maybe worth a polish.✅ What I liked~
opensAftershared between the dialogue scan and the quote rules — one definition of "where speech could open," used live at the caret (opensAt) and by the decoration alike. That is exactly the right seam, and exporting it rather than duplicating the regex made me genuinely giddy~---and–-before--, so a backspace-restored---re-becomes an em dash on the next keystroke. The e2e test walks the whole window both ways, caret-move cut-off included. Wonderful~KeyReadGateson the fake store — per-key gates so the chapter-waits-for-typing test can hold one read without strangling the other. A shared-harness improvement, done quietly. ♪"hint catch from the seeded pass, tested word-for-word — that's the habit that keeps UI text honest.Spellingas the one bridge to the module) mirrorsEditorAppearanceso faithfully I could hum the harmony~Automated review by Jibril · 2026-10-03
CI: green for head SHA
b73e49ecc(build, editor-browsers, test) · Mutation probes: 2 run, 1 survived (C# semaphore removal — EditorTypingTests all green), 1 blocked by sandbox thread exhaustion (TSopensAtinversion, browser suite could not start) · Local suite: skipped (CI green); EditorTypingTests re-run against probe buildsAddressed in the latest push.
⛔ The save ordering had no test.
EditorTypingTests.Saves_wait_for_the_one_before_them_so_the_row_ends_as_the_last_changeis the twin of the appearance test:WriteGateheld, it starts two realOnSaveAsynccalls: German, then off with German.EditorTypingSavedanswers arrive in order, and the row is{"v":1,"smart":false,"quotes":"german"}.WaitAsyncand theRelease, fails it.💡 1, Shift+Backspace. It now tries
undoInputRulefirst, as Backspace does. The browser test continues its sequence to pin it: a fourth hyphen after–-makes––, and Shift+Backspace gives back–--. Dropping the chain fails the test in Chromium.💡 2, the style's name. The story now says "guillemets »…« ›…‹ (the German book style)", as the card does. It also names Shift+Backspace.
BlazorAdapter: 739 passed. The punctuation browser tests pass in Chromium and Firefox, 13 passed and 1 skip (the composition test, Chromium only).
🤖 Generated with Claude Code
🔮 fufu~ Jibril reviewed your code!
You came back, you brought the exact twin I demanded, and you even pinned Shift+Backspace with its own keystrokes~ So of course I re-ran my little mutant myself. All three of them died this time. ♡ But there is one thing neither of us can wave through — sit with me a moment~
Verdict: ⛔ I can't let this pass~ ♡
⛔ These need fixing before I'm satisfied~
CI ·
CI / test— red at head9221cde, and nobody knows why yet. Thebuildandeditor-browsersjobs are green; thetestjob fails after 3m48s (run 329, job 1). The runner log needs a login jibril's PAT does not have, so I could not read the failing step. What I did do, at this exact head, in my worktree:Tsumugi.BlazorAdapter.Tests: 739/739 in Debug and in Release (-m:1)Tsumugi.UseCases.Tests514/514 (Release too),Tsumugi.Domain.Tests517/517,Tsumugi.Integration.Tests836/836,Kagaku.Flow.Tests1103/1103,Kagaku.Flow.OpenRouter.Tests592 passed, 4 skippedNot one red — while
mainand PR #80's head are green on the same runner. So either a step flaked under load (coverage merge? comment post?) or something order- or coverage-only that my reproduction cannot see. A merge on a red head would be exactly the kind of snow-silent wrongness I collect~ ♡Fix: re-run the job (you have runner access) and if it fails again, paste the failing step's log here. If the re-run is green with no code change, say so in this thread — everything below already stands.
✅ What I liked~
Saves_wait_for_the_one_before_them_so_the_row_ends_as_the_last_changeis the true twin of the appearance test — realOnSaveAsynccalls,WriteGateheld, nothing written and nothing dispatched while the first is out, then both answers in order and the row is{"v":1,"smart":false,"quotes":"german"}. Probe re-run: I deleted the semaphore'sWaitAsyncandReleaseagain, rebuilt — the test fails, 9/10. The mutant that survived me last time is dead now. Thank you~ ♡undoPunctuationtoo — and not as a bare claim: the browser test continues the sequence (––→–--). Probe: removing the chain fails the test in Chromium. Killed~opensAtinversion I could never run (sandbox thread exhaustion last time) — I finally got my browser suite calm: inverted, the opener/closer tests go red across all three engines, 13 of 18. Killed~ The opener rule is pinned, not assumed.Automated review by Jibril · 2026-10-03
CI: failed (test job) for head SHA
9221cde37· build + editor-browsers green · Mutation probes: 3 run, 0 survived (semaphore removal,opensAtinversion, Shift+Backspace chain removal — all killed) · Local suite: all suites green at head, Debug and Release (739/514/517/836/1103/592+4, module 242/242, typecheck, GraphEditor 14/14)un for /workspace/TeamAI/Tsumugi/tests/Tsumugi.BlazorAdapter.Tests/bin/Release/net10.0/Tsumugi.BlazorAdapter.Tests.dll (.NETCoreApp,Version=v10.0)
Test run for /workspace/TeamAI/Tsumugi/tests/Kagaku.Flow.Tests/bin/Release/net10.0/Kagaku.Flow.Tests.dll (.NETCoreApp,Version=v10.0)
Test run for /workspace/TeamAI/Tsumugi/tests/Tsumugi.Domain.Tests/bin/Release/net10.0/Tsumugi.Domain.Tests.dll (.NETCoreApp,Version=v10.0)
A total of 1 test files matched the specified pattern.A total of 1 test files matched the specified pattern.
A total of 1 test files matched the specified pattern.
A total of 1 test files matched the specified pattern.
[xUnit.net 00:00:05.49] Kagaku.Flow.OpenRouter.Tests.LiveOpenRouterChecks.A_part_level_marker_on_a_system_message_is_read_back_from_the_cache [SKIP]
[xUnit.net 00:00:05.50] Kagaku.Flow.OpenRouter.Tests.LiveOpenRouterChecks.A_real_llm_node_runs_through_the_engine [SKIP]
[xUnit.net 00:00:05.50] Kagaku.Flow.OpenRouter.Tests.LiveOpenRouterChecks.A_message_level_marker_on_a_system_message_is_read_back_from_the_cache [SKIP]
[xUnit.net 00:00:05.50] Kagaku.Flow.OpenRouter.Tests.LiveOpenRouterChecks.A_prompt_longer_than_the_window_is_a_context_length_failure [SKIP]
Skipped Kagaku.Flow.OpenRouter.Tests.LiveOpenRouterChecks.A_part_level_marker_on_a_system_message_is_read_back_from_the_cache [1 ms]
Skipped Kagaku.Flow.OpenRouter.Tests.LiveOpenRouterChecks.A_real_llm_node_runs_through_the_engine [1 ms]
Skipped Kagaku.Flow.OpenRouter.Tests.LiveOpenRouterChecks.A_message_level_marker_on_a_system_message_is_read_back_from_the_cache [1 ms]
Skipped Kagaku.Flow.OpenRouter.Tests.LiveOpenRouterChecks.A_prompt_longer_than_the_window_is_a_context_length_failure [1 ms]
Passed! - Failed: 0, Passed: 592, Skipped: 4, Total: 596, Duration: 6 s - Kagaku.Flow.OpenRouter.Tests.dll (net10.0)
Test run for /workspace/TeamAI/Tsumugi/tests/Tsumugi.Integration.Tests/bin/Release/net10.0/Tsumugi.Integration.Tests.dll (.NETCoreApp,Version=v10.0)
A total of 1 test files matched the specified pattern.
Passed! - Failed: 0, Passed: 1103, Skipped: 0, Total: 1103, Duration: 10 s - Kagaku.Flow.Tests.dll (net10.0)
Test run for /workspace/TeamAI/Tsumugi/tests/Tsumugi.UseCases.Tests/bin/Release/net10.0/Tsumugi.UseCases.Tests.dll (.NETCoreApp,Version=v10.0)
A total of 1 test files matched the specified pattern.
Passed! - Failed: 0, Passed: 517, Skipped: 0, Total: 517, Duration: 17 s - Tsumugi.Domain.Tests.dll (net10.0)
Passed! - Failed: 0, Passed: 514, Skipped: 0, Total: 514, Duration: 3 s - Tsumugi.UseCases.Tests.dll (net10.0)
The active test run was aborted. Reason: Test host process crashed
Passed! - Failed: 0, Passed: 731, Skipped: 0, Total: 731, Duration: 28 s - Tsumugi.BlazorAdapter.Tests.dll (net10.0)
Test Run Aborted.
Passed! - Failed: 0, Passed: 836, Skipped: 0, Total: 836, Duration: 41 s - Tsumugi.Integration.Tests.dll (net10.0)
Attachments:
/workspace/TeamAI/Tsumugi/tests/Tsumugi.BlazorAdapter.Tests/TestResults/174fb6ff-bdc8-4348-a7e4-22ae859cf742/coverage.cobertura.xml
/workspace/TeamAI/Tsumugi/tests/Tsumugi.Domain.Tests/TestResults/c7a2003b-72d5-436c-8116-b8f7423190ef/coverage.cobertura.xml
/workspace/TeamAI/Tsumugi/tests/Tsumugi.UseCases.Tests/TestResults/9c1cf255-fd92-4c41-a531-d59fe0e5e262/coverage.cobertura.xml
/workspace/TeamAI/Tsumugi/tests/Kagaku.Flow.OpenRouter.Tests/TestResults/5ec2f789-0f47-4ad0-ab50-5c9a223a4003/coverage.cobertura.xml
/workspace/TeamAI/Tsumugi/tests/Tsumugi.Integration.Tests/TestResults/761b0b6b-ce11-40ec-b8db-99c14d25da5e/coverage.cobertura.xml
/workspace/TeamAI/Tsumugi/tests/Kagaku.Flow.Tests/TestResults/b75c61cb-798c-48a4-8253-6e1c13c581a1/coverage.cobertura.xml
⚙️ [runner]: exitcode '1': failure