feat: Phase 1 · 7/7 — the documents panel and the seed data #11
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/p1-documents"
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?
Slice 7 of Phase 1: the Documents tab in the workspace's side pane and the sample world for browser verification (
docs/stories/documents.md,AGENTS.md; ADRs 0011, 0013, 0017, 0022). One commit,f50c9df.What's in
DocumentsPanel,CategoryEditor): created from the suggestions row (the sixSuggestedCategory.Allentries, each offered until it exists) or as a blank "New category" opened to be named; renamed, described, default mode, placement (Header / Near end with a distance), reordered from their menu (Move up / Move down, edges disabled), deleted behindDeleteCategoryDialog— aModalwith its own footer: Move and delete to a chosen category, or Delete with documents; an empty category confirms plainly. ANevercategory and aNeverdocument wear a Private badge; every mode and placement has a one-line explanation (ModeLabels, from ADR 0013's table) as the select's hint and the badges' titles.DocumentEditor,TriggerChips): title, description, body, category (a move, not a save), mode override (ModeChoiceselect — "Inherit (Triggered)" spells the category's default; inherit storesnull, never a mode), triggers as chips composed fromBadge+Button+TextField(Enter or Add; normalised exactly asDocument.SetTriggersdoes, so a second spelling collapses), priority as a number field, the provenance of the last edit ("Last edited by you" / "by a run (node · model)" / "…, then by you"), delete behind aConfirmDialog. The list groups by category, in the store's order (priority, then title, ordinal), with aDebouncedSearchFieldover titles, descriptions and triggers that keeps every category header.Debounceon the injected clock, one journaled operation per pause, the whole profile every time (UpdateDocument/UpdateCategoryhave no partial writes); a discrete pick — mode, a chip, the placement — saves at once. Idle-only adoption with the trimmed-echo guard (the domain trims title/summary/body and tidies triggers), a phantom-edit guard (the same text saved again journals nothing), a blank title or an unreadable number refused under the field with the pause kept. Back saves first; Delete discards, never flushes against a row about to go.TriggerScan(UseCases) builds ADR 0017's default window — the last 20,000 characters of the chapter on screen (paragraphs a blank line apart, scene breaks as***) plus the header documents' bodies — andIsActivesays Always → yes, Triggered → a trigger in the window, OnDemand / Never → never.TriggerMatch(Domain) is the one case-insensitive substring rule;Document.IsTriggeredBydelegates to it. The panel reads the chapter on screen from the workspace slice and recomputes only when that chapter or the view changes, so the badge follows a typing pause and a chapter switch with no extra query. The Phase 2 context node and its preview reuse the scanner; the budget cap and node exposure are theirs.Documents/):DocumentsState(keyed by story;Filtered, the open document/category), one action record per file (19),DocumentsReducers(every reducer guards onStoryId; a load landing closes an editor whose record vanished and clears its refusal; a saved category recomputes the effective mode of its inheriting documents; a saved document is re-sorted within its category),DocumentsEffects(typed saves patch, structural requests re-query explicitly,DomainChangesReceivedreloads onDocument/DocumentCategoryonce the panel has loaded — the registry pair AGENTS.md demands — and every answer dispatches the newUndoStatusChangedso the Undo button follows document edits;UndoTypinghas no kind filter, so a document edit is what Ctrl+Z takes back).Tabs Param="tab"deep-links both; the page'sSaveIndicatorcovers the panel's dirtiness and its refusals (a refusal outranks a save in flight);FlushAsync— Ctrl+Z / Ctrl+Y, Save a version, Revert, Redo — flushes the open editor too.WorkspaceReducers.OnUndoStatusChangedis the one reducer added to the workspace slice.SeedDevData,Program.cs): behindTSUMUGI_SEED_DEV_DATA=true, read through configuration like the other switches, run right after the migration inside its own scope, only into a database with no live story (a trashed-only one still seeds; a second start logs "Seed skipped"). It creates Mira's Winter — three chapters (summaries on the first two, none on the last), a scene break, paragraphs by the writer and two by a run under an explicit Run scope (no Phase 1 use case stamps run provenance), one of them edited afterwards — a category per default mode (Style, Author's note near the end, Characters, Lore, a custom on-demand Background, Notes to self) with documents whose triggers match chapter 1 (Mira), chapters 2–3 only (the sister, Ilse), or nowhere (Warden), one mode override (The Winter Pact, Always), two snapshots (Your edits before the run, the run's) with edits since — and The Lantern Road, archived. Everything through the real use cases, each under its own scope; no OpenRouter key.README's status line andAGENTS.md's status paragraph and seed section are updated to match.documents.mdboxes for categories, suggestions, delete-asks, documents, the list, provenance/active (the suggestions list now names the six the code ships; a note says what the Phase 1 badge scans), REQUIREMENTS §3's categories, documents and chapter-summaries boxes.Deferred and why
contextnode —TriggerScanis written for it.UpdateDocumentstampsProvenance.User, andDocument.Updateassigns the provenance rather than marking an edit, so "run, then you" cannot occur for documents until a run writes one (Phase 4). The seed leaves documents user-written; the display code and its tests cover the run shapes through the store.Tests
New:
TriggerMatchTests(4),TriggerScanTests(5),SeedDevDataTests(8: the world's contract, a category per mode with matching and non-matching triggers and one override, two snapshot kinds with edits since, noSystemrow and every row labelled, the live-story guard, a trashed-only database seeding, the provider key unset, the host flag on and off throughGatedAppFactory { Seed = true }),DocumentsReducersTests(8, incl. every one of the 19 actions dropped for a story the panel has left),DocumentsEffectsTests(6),DocumentsPanelTests(20, the whole story through the real pipeline over the fakes: suggestions, the grouped list with its marks and ordinal order, the filter, editing with the pause, the trimmed echo, the blank-title and bad-number refusals, chips, modes, the move, create/delete, delete-category both ways, reorder, the category editor with the distance, the badge following the chapter and a header body, another session's change mid-typing, a document deleted elsewhere, the flush on leaving),ModeLabelsTests(3),WorkspacePageTests+4 (the tab order, a document save refreshing the Undo button, Ctrl+Z flushing a document pause then undoing, a refused document save in the page's indicator; the History test now clicks its tab),WorkspaceReducersTests+1. Doubles:FakeDocumentStore(the EF store's boundary checks, the cascade, ordinal titles) wired intoFakeWorldandAdapterTestContext.Mutation probes (each disabled by hand, the named test fails, restored): the
StoryIdguard onOnDocumentSaved; the editor's echo guard; theDocumentCategoryarm of the doorbell effect;IsActivefor OnDemand; the header bodies inWindow; the seed's live-story guard; the dialog's move target;Discard()before a delete; inherit → Always. A tenth — the fake sorting titles culture-aware — is inconclusive by construction:Directory.Build.propssetsInvariantGlobalization, under which culture comparison is ordinal.Coverage (coverlet, merged across test projects, paths normalised)
BlazorAdapter/Documents/**,WorkspacePage.razor,WorkspaceReducers.cs,UndoStatusChanged.cs,TriggerScan.cs,SeedDevData.cs,TriggerMatch.cs,Document.cs,Program.cs: 857 / 861 lines, 560 / 592 branches. Not covered, and why:SeedDevData.cs139–143 —Value<T>'s refusal and unknown-result arms: the seed's world is fixed, a refusal is a bug in the seed.CategoryEditor.razor144–146 — the toggle group's null value: it has no "all" option.DocumentsPanel.razor243–245 —Move's out-of-range guard: the menu disables the edges.DocumentEditor.razor164–182,CategoryEditor.razor120–132,TriggerChips.razor39 — thevalue ?? ""arms onValueChanged; the design system never reports null.CategoryEditor.razor81 — the placement fallback for an unreadable distance while the stored placement is Header: the toggle saves NearEnd(0) at once, so the stored one is never Header by then.DocumentsPanel.razor37, 165;DocumentsReducers.cs13, 140;WorkspacePage.razor165–174;Program.cs25, 37, 100 — one combination each of a null-coalescing fallback, the two-slice dirtiness||, the comparer's equal-priority branch as coverlet counts a lambda, and configuration keys present vs absent.Browser-verified (Release build, Chrome via Playwright, the seed, light and dark)
Sign in → the list shows Mira's Winter (the archived one behind the toggle) → open it: Documents is the active tab, six categories in seed order with their mode badge, placement and description, only Memory left in the suggestions row, Ideas Private; chapter 1 on screen: Voice, Steer, Mira, The Winter Pact Active, the rest not → chapter 2: The sister and Snow gain Active, back to 1 they lose it → "+ Memory" → its menu → Edit → rename "Memory & facts", Near end, distance 3 → Saved → Back shows "Near end · 3" → Characters "+" → "Untitled document" opens: title, description, body, Enter adds the trigger Ilse, " ilse " collapses into it, mode Never → Private, back to Inherit, priority 5, category → Lore → the row moves under Lore → filter "sister" → three rows (two by description, one by title), every header kept → Delete Background → the dialog → Move to Lore → The Valley under Lore → Delete Memory & facts (empty) → one plain button → the Undo button reads "Undo: User — deleted category 'Memory & facts' with its documents", Ctrl+Z brings it back, Ctrl+Y removes it → History lists the document operations with their labels → Revert to Your edits takes the run's paragraphs and every document change back → Redo returns them → a second tab: a rename in A reaches B's list, and B's body typed meanwhile survives A's save → dark mode: the list, the editor with chips → restart with the flag: "Seed skipped: a live story exists".
Follow-ups
contextnode;Document.Update→AfterUserEdit()when runs write documents; a Kagaku.UI Chip if a third feature needs one.🤖 Generated with Claude Code
Summary
Summary
Coverage
Tsumugi.BlazorAdapter - 97.1%
Tsumugi.Domain - 99.4%
Tsumugi.Infrastructure - 97.9%
on
tion
n
Tsumugi.Kernel - 90.9%
Tsumugi.Server - 100%
Tsumugi.UseCases - 97.9%
🔮 fufu~ Jibril reviewed your code!
Oh? Oh my~ The last Phase 1 slice, and what a weave it is! Fifty-nine files, a whole Fluxor slice with nineteen actions, a scanner shared with Phase 2's context node, and a little sample world with two sisters and a broken pact… I read every line of the diff and then every changed file whole, and I could not put it down~ ♡
Verdict: ✅ Looks good to me~
Verified by me, mechanically (CI has no run for
f50c9dfyet — no bot comment, no status — so I ran everything locally):TreatWarningsAsErrors(after initializing both submodules — the OpenRouter.Net pinfd43f04is required, MSB9008 otherwise).return false;→;) →Skips_a_database_with_a_live_story…red. (Full removal doesn't compile — CS9113 unreadstories— the guard is load-bearing twice over~)IsActiveOnDemand arm flipped to trigger-match →Always_is_active…red.Windowdrops header bodies → 3 TriggerScan tests red.OnDocumentSavedStoryId guard removed →Every_answer_for_a_story_the_panel_has_left…red.A_trimmed_echo…red.Delete()flushes instead of discarding → the doomed-row test red — the pause really is kept from writing against a row about to go._target→ green, and correctly so: the shipped button hardcodesnull; my mutation only changes behavior on an interleaving the dialog's own flow makes unreachable-with-a-target. The move arm itself is pinned (Move and deleteasserts the Valley lands under Lore).DocumentCategoryarm →A_loaded_panel_reloads…red — the AGENTS.md registry pair is honored.OnLoadedkeeps vanished editors open →A_load_landing_closes…red.Mode(ModeChoice.Inherit)returns a mode → two tests red — inherit storesnull, never the category's default, exactly as ADR 0013 says.Sibling parity, traced not assumed: the doorbell effect is line-for-line the ManuscriptEffects/HistoryEffects shape (
View is not null+ kind check, registry constants already declared);UndoStatusChangedfollows the workspace slice's StoryId-guard rule;Debouncedisposal inDispose()withDiscard()before delete matches the Kagaku.UI contract; the reducer's ordinalByPriorityThenTitlematches both the EF store's SQLite collation and the fake'sStringComparer.Ordinal— no culture seam underInvariantGlobalization. The seed drives only real use cases, each under its own scope, with the one direct port write under an explicit Run scope — and the journals-never-as-System test pins it.💡 Little ideas (non-blocking)~
DocumentsPanel.razor+DocumentEditor.razor—ProvenanceIcon/ProvenanceTitlevsProvenanceIcon/ProvenanceTextare the same two switches twice (10 lines each). Both copies are test-pinned today, so nothing can drift silently — but when Phase 4 makes runs write documents and the wording grows, extract a).ProvenanceLabelsbesideModeLabels(the codebase already has the precedentDocumentEditor.Matches/CategoryEditor.Matches— the echo guard compares the priority/distance as canonical strings, so a non-canonical-but-valid spelling (+5,05) parses, saves correctly, then failsMatcheson the echo and the field rewrites to"5"under the caret for one frame. No wrong data ever lands — comparing the parsed ints instead would close that last cosmetic seam.✅ What I liked~
TriggerScanplaced in UseCases, pure, shared — the badge today, thecontextnode tomorrow, one rule (TriggerMatch) in the Domain whereDocument.IsTriggeredBydelegates to it. No copy of the trigger rule exists anywhere else; mutation probes confirm every clause is pinned.ReferenceEqualson chapter + view) — recomputes exactly when something it reads changed, no extra queries per row.OnLoadedclosing editors whose records vanished — with the save refusal cleared andSaveState.Savedrestored, tested for both editor kinds.Value<T>("a refusal is a bug in the seed"), the trashed-only-still-seeds rule, and header bodies worded not to leak another document's trigger — the world is designed so the Active badge depends on the chapter alone, and the integration test asserts exactly that per chapter.Phase 1 is one slice from closed. Weave the backups, scarlet~ I'll be watching. ♪
Automated review by Jibril · 2026-09-23
CI/CD: absent for head SHA
f50c9df· Local checks: build 0w/0e (warnaserror), 356/356 tests, 10 mutation probes (8 red-on-target, 2 analyzed above)