fix(assistant): a positionless enter refuses loudly instead of vanishing from the stage #157
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/enter-needs-a-position"
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 bug
Assistant-written scene scripts could put a character "on stage" who never rendered — no sprite in the player, no cast in the editor's effective-stage summary. Seen live on staging (Seasick Island / The Tempest / Airplane flight): Goto and Steven had
Enterchanges, yet the player showed only the background.The chain:
SceneScript.BuildStageaccepted an enter with noposition— validation only rejected unknown position strings, so an omitted one was stored asnull.SceneStageProjection.Foldtreats an enter without a position as ill-formed and drops it (by design — the fold is total and self-healing).Center(change.Position ?? StagePosition.Center), so the broken row looked like a complete "Enter · Goto · Center" change.The fix
BuildStage: an enter without a position now refuses loudly, naming the fix like the other stage guards ("…enters without a position — an enter needs one: left, centerLeft, center, centerRight, or right."). Coverswrite_scene_linesandset_scene_line_stagealike; both tool descriptions now state the requirement up front.SceneLineStage.razor: a null position renders as the disabled "Position" prompt (adefaultvalue matches no option), so pre-fix rows stay visibly incomplete instead of masquerading as center — picking a position heals the row.Every_stage_guard_refuses_with_the_fix_named) gains the positionless-enter refusal.The rejection deliberately does not live in
SetSceneStepStage: the editor auto-saves mid-edit states (an enter with no character picked yet is normal), and the assistant tools swallow the use case's error text anyway — the tool boundary is where a refusal can tell the model what to do instead.Modifysemantics are untouched (null position = leave it as it was).Full solution builds clean; all 1,621 tests pass. The affected staging rows were already healed by re-saving their positions through the editor.
🤖 Generated with Claude Code
Summary
Summary
Coverage
Kagura.BlazorAdapter - 88.7%
Kagura.Domain - 95.4%
D04ADFED3A21D401C2764A1D17367E35BEB556CBB3B4B0B74__NonSlugChars_0
Kagura.Infrastructure - 97.6%
n
on
C689CA5BBE6D1849B2CA79581BBB07D40636D85790C0E51A9__ArtifactTimestampRegex_2
C689CA5BBE6D1849B2CA79581BBB07D40636D85790C0E51A9__ProjectRoute_0
C689CA5BBE6D1849B2CA79581BBB07D40636D85790C0E51A9__TabQuery_1
Kagura.Kernel - 90%
Kagura.Server - 82.8%
Kagura.UI - 94.9%
Kagura.UseCases - 96.2%
🔮 fufu~ Jibril reviewed your code!
Oh? Oh~! A character who walks on stage and vanishes — no spotlight, no sprite, just a ghost in the background? That's not a bug, that's a tragedy! ♡ But you caught it, traced the whole three-step chain, and refused to let the fold keep swallowing it silently. I'm genuinely delighted~
Verdict: ✅ Looks good to me~
This is a clean, surgical fix. I read the full
BuildStage, theFoldprojection, theSelectcomponent, the domain contracts, and both tool paths — everything lines up. Let me show you my work~ ♡The guard is exactly right. It sits after the
ParsePositioncheck (soposition: "balcony"still rejects as "unknown"), and it only fires forEnter. TheFoldconfirms why: aModifywith a null position correctly means "leave it" (change.Position ?? current.Position), andLeaveignores position entirely. Your PR description even calls this out explicitly. TheStageChange.cscontract says position is "required to enter" — the code now enforces what the docstring always promised. Fufu~ beautiful.The Razor trick made me giggle.
change.Position ?? defaultwheredefault(StagePosition)=0— and since the enum starts atLeft = 1, that0matches no option inPositionOptions, soSelect.SelectedIndexreturns null and the disabled placeholder shows. I traced it throughSelect.razorlines 65–84 to be sure. The old?? StagePosition.Centerwas the liar that made broken rows look complete — this fixes the masquerade without touching the component. Elegant. ♪Coverage is honest. CI ran (forgejo-actions coverage comment is live for
1fbfe3c), andKagura.Infrastructure.Assistant.Tools.SceneScriptsits at 100% line / 100% branch — so the new guard lines are exercised, not just compiled. The guard-walk test (Every_stage_guard_refuses_with_the_fix_named) gains the positionless-enter refusal, sitting right next to its sibling guards with the same "name the fix" pattern. Consistency~! ♡✅ What I liked~
Fail("…— <fix>.")shape, same naming of the remedy. A reviewer can see at a glance it belongs.BuildStage) and not inSetSceneStepStage(the editor auto-saves mid-edit states). That's the right call: refuse where you can tell the model what to do instead.write_scene_linesandset_scene_line_stagenow state the requirement up front. The assistant learns the rule before it can break it. ♪Automated review by Jibril · 2026-07-16
CI/CD: passed for head
1fbfe3c(coverage comment present,SceneScript100%/100%, new guard lines covered) · Local checks: skipped (CI present and green)