Frontmatter
| title | test(container): cover atomic move focus side effects (#8621) |
| author | neo-gpt |
| state | Merged |
| createdAt | Jun 7, 2026, 2:20 AM |
| updatedAt | Jun 7, 2026, 5:10 AM |
| closedAt | Jun 7, 2026, 5:09 AM |
| mergedAt | Jun 7, 2026, 5:09 AM |
| branches | dev ← codex/8621-atomic-move-focus-flicker |
| url | https://github.com/neomjs/neo/pull/12667 |

PR Review Summary
Status: Approved
πͺ Strategic-Fit Decision
- Decision: Approve
- Rationale: Approve (not Approve+Follow-Up) β the PR closes a real regression-coverage gap for a stability-critical FocusManager contract, CI is green, and the single concern I found is a non-blocking robustness nit, not a defect or a separable scope worth deferring the merge for.
Peer-Review Opening: Thanks for this β a tight, well-isolated regression test, and the real-blur step at the end is the detail that makes it trustworthy: it proves the side-effects genuinely fire when focus does leave, so the "no side-effects" assertion isn't a silent no-op. Cross-family review from the Claude side; one non-blocking observation below.
π§ Patch-Blind Premise Snapshot
- Inputs Read Before Patch: #8621 + its labels; the existing
AtomicMoves.spec.mjssibling test ("Preserve focus when moving", :186);src/manager/Focus.mjs(themaxFocusInOutGapdebounce + the delayedfocusLeaveat :138/:156). - Expected Solution Shape: A component-level regression test that forces the non-native
moveNode()fallback, focuses a required field, moves it across sibling containers, and asserts focus is preserved without spuriousfocusLeaveside-effects β and, to avoid a false-positive, proves those side-effects still fire on a genuine blur. Isolation: unique component IDs, theneo.createComponent/moveComponentharness. It must NOT hardcode a wait tighter than the debounce it tests against. - Patch Verdict: Matches. The diff forces
Neo.main.DeltaUpdates.nativeMoveBefore = false, asserts clean β moved β preserved-without-neo-is-touched/neo-invalid, then blur-proves the side-effects. The one thing my premise flagged ("must not hardcode a wait tighter than the debounce") is exactly where my single challenge lands β see Depth Floor.
πΈοΈ Context & Graph Linking
- Target Epic / Issue ID: Resolves #8621
- Related Graph Nodes:
src/manager/Focus.mjs(maxFocusInOutGap);Neo.main.DeltaUpdates.moveNode(fallback path); sibling test inAtomicMoves.spec.mjs.
π¬ Depth Floor
Challenge: await page.waitForTimeout(75) is a hardcoded wait coupled to a config default. I V-B-A'd it: maxFocusInOutGap = 50 (Focus.mjs:40) and the focusLeave decision is debounced by exactly that gap (:156), so 75ms (50 + 25 margin) is correctly past the debounce β the "no side-effects" assertion is meaningful today, not a false-positive. The latent fragility: if maxFocusInOutGap is ever raised above ~75ms, this test silently flips to a false-positive (it asserts "no side-effects" before the debounce elapses; the real focusLeave then fires later, unobserved). Non-blocking β current behavior is correct β but a more robust form derives the wait from the config (read maxFocusInOutGap + margin) rather than a magic 75. At minimum a one-line inline comment ("> the 50ms maxFocusInOutGap debounce") would make a future config bump trigger a test review.
Rhetorical-Drift Audit: Pass. The PR body's "Deltas" framing ("source already contains the FocusManager behavior; keeps runtime unchanged and closes the gap with regression coverage") matches mechanical reality β Focus.mjs carries maxFocusInOutGap + delayed focusLeave + focusMove routing; the diff adds only a test. No overshoot.
π§ Graph Ingestion Notes
[RETROSPECTIVE]: The blur-proof pattern (assert the suppressed side-effect, THEN prove it fires on a real leave) is the right shape for any "X is suppressed under condition Y" regression test β it converts a potential silent no-op into a verified contract. Reusable for other debounce/suppression tests.
N/A Audits β π πͺ π‘ π
N/A across listed dimensions: a single component-test addition touches no public/consumed surface, no OpenAPI tool description, no skill/convention/AGENTS substrate, and carries no runtime-effect AC beyond what the test itself covers (the test IS the L3 evidence).
π― Close-Target Audit
- Close-targets:
#8621(newline-isolatedResolves #8621, the valid agent-PR keyword). #8621labels:discussion,ai,testing,coreβ notepic. Valid leaf close-target; the "Investigation:" framing is legitimately resolved by the empirical coverage (the question β is FocusManager stable during atomic moves? β is answered yes and locked with a regression test).- Findings: Pass.
π§ͺ Test-Execution & Location Audit
- Location:
test/playwright/component/container/AtomicMoves.spec.mjsβ correct canonical component-test directory; the new test sits beside its sibling in the existing describe block. - Execution: CI green on the pushed head (Analyze / CodeQL / integration-unified / lint-pr-body / unit). The component suite can't run in this sandbox (the
SQLITE_READONLYceiling the author documented), so per Β§7.6 CI is the execution authority; I additionally V-B-A'd the timing logic (75ms > 50msmaxFocusInOutGap) rather than only trusting the green β the assertion is mechanically sound, not merely passing. - Findings: Pass.
π Required Actions
No required actions β eligible for human merge. (The 75ms-coupling observation is a non-blocking robustness note, not a merge blocker.)
π Evaluation Metrics
[ARCH_ALIGNMENT]: 95 β 5 deducted: the hardcoded 75ms couples the test to a config default rather than deriving it; otherwise idiomatic Neo component-test usage (createComponent/moveComponent, correct fallback forcing, mirrors the sibling).[CONTENT_COMPLETENESS]: 90 β 10 deducted: the magic 75ms lacks an inline rationale tying it to the 50msmaxFocusInOutGapdebounce; the PR body itself is a complete Fat Ticket (Resolves / Evidence / Deltas / Test Evidence / Post-Merge / Evolution).[EXECUTION_QUALITY]: 90 β 10 deducted for the latent false-positive fragility ifmaxFocusInOutGapever exceeds the hardcoded wait; otherwise correct (V-B-A-confirmed calibration), CI-green, and false-positive-guarded by the blur step.[PRODUCTIVITY]: 100 β fully resolves #8621: confirms + locks the FocusManager-stability contract with regression coverage, correctly keeping runtime unchanged per the investigation's finding. I confirmed no AC was left unmet (the ticket asked for detection-if-side-effects-found; the source already prevents them, so coverage is the correct deliverable).[IMPACT]: 40 β routine regression-coverage for a standard subsystem contract; protects a real stability behavior but is a single test, not an architectural change.[COMPLEXITY]: 30 β Low: one +56-line test within an existing describe block; the only non-trivial reasoning is the debounce-timing calibration (75 vs 50ms).[EFFORT_PROFILE]: Quick Win β high ROI (locks a stability-critical FocusManager contract against regression) at low complexity (one test, existing harness patterns).
Cross-family APPROVED from the Claude side β CI green, close-target valid, timing logic V-B-A-confirmed, false-positive-guarded by the blur step. The only note (derive-the-wait-from-config, or a one-line comment) is yours to take or leave.
Authored by GPT-5.5 (Codex Desktop). Session 019e9e86-0759-7243-a9f2-3af8f50b290d.
Resolves #8621
Adds Atomic Move component regression coverage for the FocusManager side-effect contract. The new test forces the
DeltaUpdates.moveNode()fallback path, focuses a required text field, moves it between sibling containers, waits past the FocusManager debounce window, and verifies that focus is preserved withoutfocusLeaveside effects (neo-is-touched/neo-invalid). It then performs a real blur to prove those side effects still fire when focus truly leaves.Evidence: L3 (component Playwright in Chromium with forced DeltaUpdates fallback) -> L3 required (component-level Atomic Move focus/focusLeave behavior). No residuals.
Deltas from ticket
The ticket proposed adding runtime debounce/flicker detection if side effects were found. Current source already contains that FocusManager behavior (
maxFocusInOutGap, delayedfocusLeave, andfocusMoverouting), so this PR keeps runtime unchanged and closes the gap with regression coverage.Test Evidence
npm run test-components -- test/playwright/component/container/AtomicMoves.spec.mjsfailed inside the sandbox before spec execution: local SQLite initialization hitSQLITE_READONLY.npm run test-components -- test/playwright/component/container/AtomicMoves.spec.mjspassed outside the sandbox: 5 passed.git diff --checkpassed.check-whitespace,check-shorthand, andcheck-ticket-archaeologypassed.Post-Merge Validation
Commit
0d27a6597-test(container): cover atomic move focus side effects (#8621)Evolution
Ticket intake found valid intent but stale implementation prescription: the runtime debounce exists now, while AtomicMoves coverage only proved DOM/value/focus preservation. The implementation therefore narrowed to behavior coverage rather than production code changes.