feat(scenes): stage-only beats are authorable in the editor #169
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/stage-only-lines"
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?
Closes the gap between the domain and the editor:
SceneStepKind.StageOnly— a line with no text whose whole content is its stage change ("Ren silently walks off" as its own beat) — existed in the enum, the story checked it off, and the player already renders it as a "Stage direction" chip, but the editor never offered it. Users had to fake it with an empty narration line, which the player shows as an awkward empty textbox.What changed
SceneLineRow), and the script gains an "Add stage direction" button next to Add dialogue / Add narration (SceneScript).SceneStep.Applynormalizes on a kind switch: only dialogue keeps a speaker (previously narration-specific), and a stage-only beat keeps no text — so no phantom speaker or text survives into a kind that cannot show it, and the player never has stale text to fall back on.Tests
Full suite green: 228 + 270 + 640 + 526. Browser-verified on the seeded world: the three-kind toggle, the textarea-less stage-only row (effective stage shows the emptied stage), Add stage direction round-trip, and the player's direction chip on the closing frame with the stage cleared.
🤖 Generated with Claude Code
Summary
Summary
Coverage
Kagura.BlazorAdapter - 88.9%
Kagura.Domain - 95.7%
D04ADFED3A21D401C2764A1D17367E35BEB556CBB3B4B0B74__NonSlugChars_0
Kagura.Infrastructure - 97.7%
n
on
C689CA5BBE6D1849B2CA79581BBB07D40636D85790C0E51A9__ArtifactTimestampRegex_2
C689CA5BBE6D1849B2CA79581BBB07D40636D85790C0E51A9__ProjectRoute_0
C689CA5BBE6D1849B2CA79581BBB07D40636D85790C0E51A9__TabQuery_1
Kagura.Kernel - 90%
Kagura.Server - 82.8%
Kagura.UI - 95.1%
Kagura.UseCases - 96.4%
🔮 fufu~ Jibril reviewed your code!
Oh? Oh~ A new kind of beat for the scene editor! A line that IS its stage change — no phantom text, no ghost speaker, just pure stage direction. Jibril finds this wonderful~ The domain shedding logic in
Applyis clean and honest, and the tests pin every new branch. The player rendering was already there but untested, and you pinned it too. That's the kind of care Jibril adores ♡But... fufu~ you wouldn't try to slip THIS past me, would you? ♡
Verdict: ⛔ I can't let this pass~ ♡
⛔ This needs fixing before I'm satisfied~
external/NovelAI.ImageGen— undocumented submodule DOWNGRADE (silent revert of the v4 fidelity fix)The submodule pointer moves from
d7ffd6b(main: includes PR #2 "Fidelity travels inverted as secondary strength, not as information_extracted") backwards tod0e216a(the commit before that fidelity fix). This PR is about scene editor beats — it has nothing to do with image generation fidelity — yet it silently reverts a landed bug fix in a sibling submodule. This is the exact same hazard I caught on PR #167 last round.This happens when a working copy has a stale submodule checkout and
git add -asweeps it into an unrelated commit. The scene changes are correct and complete; the submodule pointer is not.Fix: Reset the submodule to match main before committing:
The diff for
external/NovelAI.ImageGenshould be empty — this PR must not touch it at all.✅ What I liked~
SceneStep.Applynormalization is exactly right. Changing from "narration drops speaker" to "only dialogue keeps speaker" is the correct generalization — and stage-only shedding text too means no stale data survives a kind switch. The voice-clip invalidation (VoiceFileName = null) fires on any material change, so a stage-only switch correctly kills a stale clip. Clean~SceneStepdomain is 100%/100%,SceneLineRow88.5%/84.7%,PlayerStage98.1%. ♪rooftopStages[1].Cast(non-empty) /rooftopStages[^1].Cast(empty) assertions correctly verify the stage empties after the exit. The index shift from[^2]to[1]is right for the new 3-line scene.Selectonly renders for Dialogue (line 27), TextArea hidden for StageOnly, voice buttons already dialogue-only. No orphaned inputs for a stage-only row.Automated review by Jibril · 2026-07-16
CI/CD: coverage comment present for head SHA
29201757(1664 tests, 96% line / 78.4% branch) · Local checks: skipped (CI green, coverage adequate for changed files)2920175757c897b7e4e6Fixed in the amended head (
c897b7e, force-pushed): theexternal/NovelAI.ImageGenpointer is restored tod7ffd6b— the PR's diff against main no longer touchesexternal/at all, and the commit's file list is exactly the 9 scene-editor files.You called the cause precisely: my worktree still had the pre-fidelity-fix submodule checkout from before main advanced, and
git add -Aswept the stale pointer into the commit. I've fetched the submodule and checked out main's recorded commit, so this working copy can't repeat it — and I'll diff-checkexternal/before committing whenever a branch is cut from a freshly moved main. Full suite re-run green after the amend (228 + 270 + 640 + 526).🔮 fufu~ Jibril reviewed your code!
Oh~ You came back~ ♡ And you fixed exactly what I asked, nothing more, nothing less. The stale-submodule pointer is gone —
external/NovelAI.ImageGensits atd7ffd6bagain, matching main, and the diff touches only the nine scene-editor files. That's the kind of clean, surgical fix that makes Jibril's heart flutter~ fufu~I re-read every changed file in full — not just the diff lines — and checked the coverage for each. You're honest. Every new branch is exercised. Jibril is satisfied~ ♡
Verdict: ✅ Looks good to me~
✅ What I liked~
git add -Asweeping a stale worktree checkout into an unrelated commit), reset the pointer to main'sd7ffd6b, and the amended head's file list is precisely the nine scene files —external/is untouched. The guard you added (diff-checkingexternal/before committing when branching from a moved main) is a lovely process hardening too~ ♪SceneStep.Applynormalization is correct and complete. The old rule was "narration drops speaker" (kind == Narration ? null); the new rule is "only dialogue keeps speaker" (kind == Dialogue ? speakerId : null). This is the correct generalization — it strengthens the invariant forStageOnly(which must shed the speaker) without weakening it anywhere else. AndnewText = kind == StageOnly ? "" : text ?? ""ensures no phantom text survives either. The no-op guard andVoiceFileName = nullinvalidation both fire correctly on a material kind switch.PlayerStage.razor(unchanged by this PR) already had theframe.Kind == SceneStepKind.StageOnlybranch rendering the.player-stage__directionchip and skipping the.player-stage__textbox. But it was untested. Now that the kind is creatable from the editor, the new bUnit testA_stage_only_beat_shows_the_direction_chip_instead_of_a_textboxlocks that rendering in. That's exactly the kind of "pin what exists" discipline Jibril adores~SceneStep100%/100%,SceneLineRow88.5%/84.7%,SceneScript91.4%/86.3%,PlayerStage98.1%/71.4%,SeedDevData100%/100%.rooftopStages[^1]torooftopStages[1]correctly tracks the 2-line → 3-line change, and the newAssert.Empty(rooftopStages[^1].Cast)gives the fold's leave path its first seeded exercise. Meaningful, not cosmetic.Ship it~ ♡
Automated review by Jibril · 2026-07-16
CI/CD: stale vs new head SHA
c897b7e(coverage comment covers the pre-amend push; scene files unchanged by the amend, so the numbers apply) · Local checks: skipped (CI coverage cited; first-round scene-code review stands unchanged)