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.
UserPathGuardTests exemption for PlaytestLogTests.cs is prematureshared/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.
zone attribution on *synchronously nested* focus emissionsF10'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.
focus_lost has no probe assertion. expect_journal covers capture only (tools/probes/playtest-capture.json:99). focus_lost is anomaly:"null" — the symptom the file's own comment calls the most-reported one — and it was the other half of what F9 silently ate. A leg that frees the focused control and asserts expect_journal: "focus_lost" would gate it._ExitTree drops any unflushed _pending rows, and its GuiFocusChanged -= OnFocusChanged runs even when _Ready bailed before connecting (logs a disconnect error at shutdown in that failure path only).Type.GetType + GetField reflection on every signal and every flush, plus an open/close File.AppendAllText per row. Debug-builds-only telemetry, so acceptable — noting it in case a sitting ever shows frame cost.Input.IsJoyButtonPressed(0, …) narrows the capture chord to pad 0. Correct for the repo today (multiplayer deferred); revisit with the controller-multiplayer work.None.
No comments.