PR #362 follow-ups: premature user:// guard exemption, zone on nested focus emissions nf-6zrh ← Beads

open priority 2 task unassigned

Twinned from https://github.com/cfsh/ninefold/issues/374 by tools/beads/import_github.py's reconcile pass.

Suggestions from ninefold-reviewer's review of #362. None of these blocked the PR — it converged at coverage=15/15, five review rounds, F1–F10 all fixed. These are the residuals that were worth writing down rather than dropping.

1. UserPathGuardTests exemption for PlaytestLogTests.cs is premature

shared/tests/UserPathGuardTests.cs:41 exempts shared/tests/PlaytestLogTests.cs with the reason *"user:// strings are test fixtures, not writes"* — but at #362's head (13825c5) that file contains zero user:// literals. The fixture the exemption is for ([InlineData("user://playtest-logs/s", …)]) is added by #368.

The exemption is by filename, so it disables the guard for the whole file from the moment #362 lands until #368 does — a just-added safety scanner silenced ahead of any need. Either move the entry into #368 (where its stated reason becomes true) or drop it and let #368 add it. Raised on three passes of the #362 review and unactioned; parking it here rather than cycling the PR again.

2. zone attribution on *synchronously nested* focus emissions

F10's fix rests on: "row N+1's signal-time ZoneNow() sample is row N's settled zone" (shared/PlaytestJournal.cs:186-193). That holds when the transitions come from separate grab calls — the TonapseHome deferred-double-grab path F10 named — because each grab completes its NOTIFICATION_FOCUS_ENTER before the next begins.

It does not hold for the SectionFocusRouter redirect, which is the other multi-row path F8 called out. ui/SectionFocusRouter.cs:76 calls want.GrabFocus() *inside* the gui_focus_changed handler, so Viewport::_gui_control_grab_focus(C1) re-enters before it reaches C1->notification(NOTIFICATION_FOCUS_ENTER). Order becomes:

``` emit(C1) → journal samples zone(C0) → router grabs C2 → emit(C2) → journal samples zone(C0) ← still C0: C1's FocusEntered hasn't run → C2 FOCUS_ENTER (ActiveZone ← zone C2) → C1 FOCUS_ENTER (ActiveZone ← zone C1) ← runs LAST ```

So the bounced-off row (C1) is stamped with the pre-nav zone rather than the zone it landed in. Practically contained today: NearestByColumn picks want inside the same section as control, so zone(C1) == zone(C2) and the final row is right; and the redirect only fires on d-pad nav (fromDpadNav), which is trigger:"input", so EscapedZone's rebuild gate keeps any false anomaly off. The cost is one mis-annotated zone field on exactly the row the journal exists to preserve — "the game bounced you off the control you navigated to".

Options: re-sample the zone at flush for all but the boundary case; guard against re-entrancy the way the router guards itself (_redirecting); or say in the contract comment that zone on a non-final batch row is best-effort. A probe leg that d-pad navs across a section boundary and asserts expect_zone would pin whichever is chosen — no existing leg produces a two-transition frame.

3. Smaller notes

Dependencies

None.

Comments

No comments.

Add a comment