feat(chapters): the full chapter page — story beats, connections, and scenes #110
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/chapter-page"
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?
The second slice of Phase 3, walking a chapter from concept to realization (chapter-page story). Builds on the chapters backbone (#104).
Story beats (on the chapter)
Chaptergains an orderedStoryBeatslist — the sharedLabeledEntrytable — carried through a newChapterProfileand persisted as one scalar JSON column (journaled and undone as a unit, like a character's traits). The chapter page's Plan section is the sharedLabeledEntriesTable, which already reorders in-table.The
Scenenode (composition child of a chapter)Scene : Entry(kindScene,ChapterId,SortOrder) — a graph node that belongs to exactly one chapter (ADR 0006/0008).ISceneStore+CreateScene(append-and-open) /ListScenes/GetScene/UpdateScene/DeleteScene/ReorderScenes;SceneDto;EntityKinds.Scene; EF config (TPT,ChapterIdFK, ordered index) +EfSceneStore+ theAddScenesmigration.Scene.ChapterIdis a typed structural ref between graph nodes, so it's registered (StructuralRefs.SceneChapter) and given a bidirectional backlink query inEfGraphStore—BacklinkCompletenessTestsrequires both. A chapter's Connections panel now lists its scenes as a derived "scenes" group.Chapter page + scene editor (BlazorAdapter)
ConnectionsPanel, and a Scenes section: aDragReorderListof scenes with create-and-open (New scene), locked while a reorder saves./projects/{slug}/chapters/{chapterId}/scenes/{sceneId}— for now the scene's concept (title + description auto-save) and delete-and-return, with the breadcrumb walking down through its chapter. The row-based script editor (SceneSteps) is the next slice, flagged in an inline note on the page.Notes
SeedDevDataTests.AddChapterStoryBeats(JSON column,defaultValue "[]") andAddScenes.Verification
🤖 Generated with Claude Code
The second slice of Phase 3, walking a chapter from concept to realization. Story beats (on the chapter): - Chapter gains an ordered StoryBeats list (the shared LabeledEntry table), carried through a new ChapterProfile and persisted as one scalar JSON column (journaled/undone as a unit). The chapter page's Plan section is the shared LabeledEntriesTable, which already reorders in-table. The Scene node (composition child of a chapter): - Scene : Entry (kind Scene, ChapterId, SortOrder) — a graph node that belongs to exactly one chapter (ADR 0006/0008). ISceneStore + Create (append-and-open) / List / Get / Update / Delete / ReorderScenes; SceneDto; EntityKinds.Scene; EF config (TPT, ChapterId FK, ordered index) + EfSceneStore + AddScenes migration. - Scene.ChapterId is a typed structural ref between graph nodes, so it is registered (StructuralRefs.SceneChapter) and given a bidirectional backlink query in EfGraphStore — BacklinkCompletenessTests requires both. A chapter's Connections panel now lists its scenes as a derived "scenes" group. Chapter page + scene editor (BlazorAdapter): - The chapter page gains a quicklink rail (Concept / Plan / Connections / Scenes), the ConnectionsPanel, and a Scenes section: a DragReorderList of scenes with create-and-open (New scene), locked while a reorder saves. - A scene editor at /projects/{slug}/chapters/{chapterId}/scenes/{sceneId} — for now the scene's concept (title + description auto-save) and delete-and-return, with the breadcrumb walking down through its chapter. The row-based script editor (SceneSteps) is the next slice. - Scenes/SceneEditor Fluxor slices mirror the chapters slice; both journaled and reversible (ADR 0020), live across sessions (ADR 0016). SeedDevData: chapter one gains story beats and two ordered scenes; asserted in SeedDevDataTests. Full suite green (1285); the scene backlink query is mutation-checked; browser-verified beats, the derived scenes backlink, scene create-and-open, and the scene editor breadcrumb. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>Summary
Summary
Coverage
Kagura.BlazorAdapter - 89.6%
Kagura.Domain - 94.1%
D04ADFED3A21D401C2764A1D17367E35BEB556CBB3B4B0B74__NonSlugChars_0
Kagura.Infrastructure - 98.4%
n
on
Kagura.Kernel - 90%
Kagura.Server - 86%
Kagura.UI - 95.7%
Kagura.UseCases - 96.1%
🔮 fufu~ Jibril reviewed your code!
A full chapter page — story beats, the Connections panel, and a brand-new
Scenenode with a bidirectional structural backlink?! Oh, this is a feast of knowledge~ ♡ I read the whole diff (all 3340 lines of it!) and traced every new piece against its sibling — the chapters backbone (#104), the character editor's labeled-entries pattern, theLocationParentstructural ref. fufu~ this is beautiful work~Verdict: ✅ Looks good to me~
Let me walk through what I scrutinized, because a PR this size deserves a thorough eye~ ♡
The
Scenenode & its structural ref —Scene : EntrywithChapterIdandSortOrder, TPT-mapped to its ownScenestable with a(ChapterId, SortOrder)index and aRestrictFK (never a DB cascade — soft delete only, ADR 0020). TheStructuralRefs.SceneChapterref is declared in the vocabulary AND queried inEfGraphStore.StructuralEdgesTouchingAsync(both directions: outbound chapter from a scene, inbound scenes to a chapter). I checkedBacklinkCompletenessTests— its model-scanning tripwire would fail loudly if either half were missing, and CI is green, so the chapter↔scene backlink is complete. The "scenes" group shows in Connections as a derived edge. fufu~ ADR 0019 honored perfectly~Story beats —
Chapter.StoryBeatsas one scalar JSON column (.AsJsonList(), exactly likeCharacter.Traits), carried through a newChapterProfile. The migration'sdefaultValue: "[]"is the right choice — an empty string would blow up JSON deserialization on existing rows. TheApply(ChapterProfile)trims each beat viaNormalized()and preserves identity, so a reorder moves the row, not its contents. TheUpdateChaptersignature change from(id, name, description)to(id, profile)is a clean widening — every call site is updated.The scene editor & chapter scenes list — both Fluxor slices mirror the chapters slice faithfully: stale-answer guards keyed by id, the create-and-open navigation, the reorder-then-reload sequence (so a second drag can't act on a stale order), the cross-session
DomainChangesReceivedreload, and the debounce-with-flush-on-dispose inSceneEditorPage. I comparedSceneEditorPage.Dispose()line-by-line withChapterEditorPage.Dispose()— identical, including theif (_dirty) Save()flush.EfSceneStore.ReorderAsyncloads the whole chapter's scenes tracked and re-seats each in one save — a single journaled, reversible operation. It usesdb.Set<Scene>().Where(s => s.ChapterId == chapterId)withoutAsNoTracking(), so the inheritedEntryConfigurationquery filter (!IsDeleted) correctly excludes trashed scenes. Identical toEfChapterStore.ReorderAsync. ♪✅ What I liked~
BacklinkCompletenessTeststripwire doing its job — addingSceneChapterto bothStructuralRefs.AllandEfGraphStore.QueriedRefskeeps the model and the queries in lockstep. Silent empty-state failure averted~ ♡ChapterProfileextraction —UpdateChapternow takes a profile, not loose strings, exactly likeUpdateLocation/UpdateCharacter. One save = one journaled operation, beats included.ChapterNameresolved alongside the scene in one load), with the quiet"…"fallback while the form hasn't synced yet. No lying to the user~SeedDevDatagaining story beats and two ordered scenes under chapter one — asserted inSeedDevDataTests, so the seeded world exercises the new paths.CreateScene100%,EfSceneStore90.6%,SceneEditorPage86.3%,EfGraphStore95.4% (the new backlink query is exercised). The integration test suite covers create/append/reorder/edit/delete + undo + cross-session announce + the chapter↔scene backlink over real SQLite. fufu~ thorough~Automated review by Jibril · 2026-07-13
CI/CD: ✅ passed for head SHA
4534edf— 1285 tests green, 96.2% line / 82% branch coverage · Local checks: skipped (CI green)