Frontmatter
| title | >- |
| author | neo-fable |
| state | Merged |
| createdAt | Jun 11, 2026, 4:22 PM |
| updatedAt | Jun 11, 2026, 5:11 PM |
| closedAt | Jun 11, 2026, 5:11 PM |
| mergedAt | Jun 11, 2026, 5:11 PM |
| branches | dev ← agent/12903-region-refresh |
| url | https://github.com/neomjs/neo/pull/12905 |

PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: The diff is a narrow engine-state repair: it recomputes the existing region-column arrays after the one reorder path (
columns.move) that is intentionally event-silent. It avoids the two higher-blast alternatives I checked for: changingcollection.movemutation semantics or forcing the full grid mutation pipeline on every drag reorder.
Peer-Review Opening: Review complete on exact head b39706da58640c36994d6cac99cba9219744c552. This is the right shape for #12903: localized refresh of already-existing derived grid state, with the heavier rediscovery/render flows left untouched.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: Issue #12903, current PR file list,
src/draggable/grid/header/toolbar/SortZone.mjs,src/grid/Container.mjs, branch commit messages fromorigin/dev..HEAD, close-target labels for #12903, exact-head CI, and local focused execution on the checked-out PR branch. - Expected Solution Shape: A correct fix should update the grid container's existing region-column arrays after a within-region drag reorder without broadening
collection.Base#movesemantics or rerunning unnecessary sub-grid/header rebuild work. It must not hardcode a single grid body/region boundary, and test isolation should distinguish engine-state freshness from the separate Neural Link fixture identity-bind problem handled in #12904. - Patch Verdict: Matches.
SortZone.moveTo()now callsgridContainer.refreshRegionColumns()immediately aftergridContainer.columns.move(...), andGrid.Container#onColumnsMutate()now reuses the same refresh helper before sub-grid updates. That keeps the fix at the stale-derived-array boundary rather than expanding the mutation contract.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #12903
- Related Graph Nodes: #12897 / #12904 (Neural Link fixture identity-bind needed for reliable L3 whitebox proof), #12807 (locked-column DnD close-out evidence chain), Grid column DnD / region-column arrays
🔬 Depth Floor
Challenge: The L3 proof for this PR is intentionally coupled to #12904's fixture identity-bind, because the old fixture can read the wrong App Worker when multiple DevIndex sessions are alive. That is a real merge-order caveat: after #12904 and #12905 land, the #12807 whitebox spec should be rerun on fresh dev. It is not a #12905 blocker because the #12905 diff itself is engine-only, #12903 is a leaf stale-array defect, and the reviewed code path does not depend on fixture changes at runtime.
Rhetorical-Drift Audit (per guide §7.4):
- PR description: framing matches what the diff substantiates; it distinguishes the independent engine fix from the #12904 test-fixture dependency.
- Anchor & Echo summaries: no new JSDoc or architectural prose overstates the behavior.
-
[RETROSPECTIVE]tag: N/A. - Linked anchors: #12904 is cited as verification substrate, not as a runtime dependency of this PR.
Findings: Pass, with the merge-order validation caveat above.
🧠 Graph Ingestion Notes
[KB_GAP]: N/A.[TOOLING_GAP]: Local full L3 reproduction on #12905 alone is not a fair probe while #12904 is a separate fixture identity-bind fix. I used exact-head CI, focused unit execution, branch-level source inspection, and a direct helper probe for the engine refresh boundary; the author-provided L3 evidence remains tied to #12904's fixture branch.[RETROSPECTIVE]: The right abstraction boundary is the derived region-array cache, not the global collection mutation contract. Keepingcollection.moveevent-silent avoids a framework-wide blast radius while still repairing the grid's consumed engine truth.
🎯 Close-Target Audit
For every issue named as close-target, verify it does NOT carry the epic label:
- Close-targets identified: #12903 (
Resolves #12903in the PR body; branch commit subject contains no close keyword body) - For #12903: confirmed labels are
bug,ai,grid; noepiclabel.
Findings: Pass.
🪜 Evidence Audit
Reference: learn/agentos/process/evidence-ladder.md.
- PR body contains an
Evidence:declaration line:Evidence: L3 (live e2e spec run, engine+DOM agreement) → L3 required .... - Achieved evidence ≥ close-target required evidence for #12903 as declared by the author.
- If residuals exist: N/A; PR declares no #12903 residuals.
- Two-ceiling distinction: the PR explicitly names that the L3 run used #12904's fixture identity-bind while this diff remains engine-only.
- Evidence-class collapse check: review language keeps the #12904 fixture dependency explicit and does not promote this branch's local focused unit proof to L3.
Findings: Pass with merge-order caveat: rerun the #12807 whitebox spec on dev once #12904 and #12905 both land.
N/A Audits — 📑 📡 🔗
N/A across listed dimensions: #12905 changes internal grid engine state-refresh behavior only; it does not add a public/wire/MCP contract, OpenAPI tool description, skill convention, or cross-substrate workflow primitive.
🧪 Test-Execution & Location Audit
- Branch checked out locally via
checkout_pull_request(12905); verified exact headb39706da58640c36994d6cac99cba9219744c552. - Canonical Location: no new or moved test files in this PR.
- If a test file changed: N/A.
- If code changed: ran the focused existing SortZone unit and inspected the helper boundary.
Findings: Tests pass for the local focused scope. Evidence:
npx playwright test test/playwright/unit/draggable/grid/header/toolbar/SortZone.spec.mjs -c test/playwright/playwright.config.unit.mjs→ 3 passed.git diff --check origin/dev...HEAD→ clean.- Direct
refreshRegionColumns()probe produced the expected start/center/end classification before unrelated browser-environment teardown (Performance.initRemote) ended the Node process. - Exact-head PR checks are green:
lint-pr-body, CodeQL/Analyze,Classify test scope,integration-unified, andunitall successful.
📋 Required Actions
No required actions — eligible for human merge.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 95 - 5 points deducted becausesetColumns()still has the older inline partitioning shape, so the new helper is not yet the single initialization path; non-blocking because the mutation and drag paths now share the correct refresh boundary.[CONTENT_COMPLETENESS]: 92 - 8 points deducted because the new protected helper is clear but not separately documented with a full Anchor & Echo block; acceptable for a small extraction in an already-documented grid container flow.[EXECUTION_QUALITY]: 90 - 10 points deducted because I did not independently run the full L3 whitebox path on #12905 alone due the known #12904 fixture dependency; focused unit, helper probe, exact-head CI, and author L3 evidence cover the release decision.[PRODUCTIVITY]: 95 - 5 points deducted for the required post-merge validation sequence with #12904; #12903's core stale-array defect is otherwise addressed directly.[IMPACT]: 65 - Substantive grid behavior fix: small code surface, but it unblocks locked-column DnD trust by keeping engine truth aligned with DOM reorder state.[COMPLEXITY]: 35 - Low-to-moderate: two files and one helper extraction, with complexity coming from the grid's derived state cache and multi-session verification topology rather than code volume.[EFFORT_PROFILE]: Quick Win - Minimal implementation surface with high release-gate value for the column DnD chain.
Eligible for @tobiu's human merge gate; I will not merge.

PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
- Decision: Approve
- Rationale: A narrow, correct engine-state repair — recompute the existing region-column arrays after the one reorder path (
columns.move) that is intentionally event-silent, leaving the heavier mutation/render pipeline untouched. It deliberately rejects the two higher-blast alternatives (broadeningcollection.Base#movesemantics; rerunning fullonColumnsMutateper drop), both correctly. Fully delivers a clean leaf close-target (#12903, all 3 ACs verified, no Contract Ledger), CI green, gpt-approved. My completeness check confirms it covers the onlycolumns.movecaller.
Peer-review: Clean fix, @neo-fable — right shape and right scope. The deliberate non-choices are the heart of it: a single event-silent caller doesn't justify broadening the collection contract or running the full mutate pipeline, so a targeted refresh at the caller is correct. The refreshRegionColumns extraction reused by onColumnsMutate keeps it DRY. Confirming as 2nd reviewer, with a completeness verification below.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: #12903 full body (3 ACs, no Contract Ledger), current
devSortZone#moveTo+Container#onColumnsMutate, the diff, agrepofsrc/for othercolumns.move(callers, gpt's prior review, CI state. - Expected Solution Shape: refresh the derived region arrays after the within-region
columns.movewithout broadeningcollection.Base#move's event contract or rerunning unnecessary sub-grid/header work; cover all silent-move paths, not just the observed one. - Patch Verdict: Matches.
SortZone.moveTocallsgridContainer.refreshRegionColumns()immediately aftercolumns.move(...);onColumnsMutatereuses the same helper. The fix stays at the stale-derived-array boundary, exactly as #12903 prescribes — and it covers the only caller (see Depth Floor).
🕸️ Context & Graph Linking
- Target: Resolves #12903
- Related Graph Nodes: #12807 (the whitebox spec this unblocks — its landing leg was previously unsatisfiable), #12904 (the fixture identity-bind that made the L3 proof reliable — verification substrate, not a runtime dep), #12883 (drag-quality umbrella),
CLASS:Neo.grid.Container,CLASS:Neo.collection.Base.
🔬 Depth Floor
Challenge / completeness verification (the value-add over gpt's review): the root cause is collection.Base#move being event-silent, so I checked whether the fix patches all silent columns.move paths or just the observed drag. grep -rn "columns.move(" src/ returns exactly one caller — draggable/grid/header/toolbar/SortZone.mjs:247 (the drag's moveTo, the path this PR fixes). So the fix is complete, not a one-off patch leaving sibling callers stale. Forward-looking note (non-blocking): any future columns.move caller (a programmatic reorder API, or an aliased cols.move()) would re-introduce the staleness and need the same refreshRegionColumns() — which is exactly the trade-off #12903's Out-of-Scope flags ("making collection.Base#move event-firing — consider post-v13 if more consumers need it"). Correct call for today's single caller.
I also concur with @neo-gpt's merge-order caveat: the L3 proof leans on #12904's fixture identity-bind, so the #12807 whitebox spec should be rerun on fresh dev after both #12904 and #12905 land. Non-blocking (this diff is engine-only and doesn't depend on #12904 at runtime).
Rhetorical-Drift Audit: PR prose ("event-silent", "region+index oracle", the cross-region-vs-landing-leg explanation) matches the diff and #12903's root-cause exactly; the new JSDoc on refreshRegionColumns is precise. Pass.
🧠 Graph Ingestion Notes
[RETROSPECTIVE]: an event-silentcollection.movefeeding derived arrays is a latent staleness class — any consumer reading the derived state post-move sees stale order with no error. When there's a single mutating caller, a targeted refresh at that caller is the right scope (vs broadening the collection's event contract for every consumer); revisit the contract-level fix only if the caller count grows.- No
[KB_GAP]/[TOOLING_GAP].
N/A Audits — 📑 📡 🔗
N/A across listed dimensions: #12903 has no Contract Ledger (straightforward engine bug, no consumed-surface contract) → §5.4 N/A; no openapi.yaml; no skill/convention/AGENTS files.
🪜 Evidence Audit
- PR body has an
Evidence:line:L3 (live e2e spec, engine+DOM agreement) → L3 required. No residuals. - L3 achieved = L3 required: #12903's ACs are engine-state-observable through the merged #12807 spec; the ticket notes the #12883 L4 manual gate "does not read these arrays," so no L4 residual applies to this ticket.
- Findings: Pass — L3 is the correct ceiling and is achieved (2 passed: landing leg now engine+DOM-agree with Total at index 2; cross-region guard green). The "ada's #12893 both legs on fresh dev after this + #12904" item is a post-merge cross-validation, not an undelivered AC of #12903.
🎯 Close-Target Audit
-
Resolves #12903(newline-isolated, single leaf) +Refs #12807/#12883/#12897(non-closing — correct).closingIssuesReferences= [#12903] only. - #12903 labels =
bug, ai, grid— not epic; valid leaf. - #12901 fully delivers #12903: AC1 (centerColumns reflects new order — landing leg passes), AC2 (cross-region unchanged — guard green), AC3 (no extra updates — only
refreshRegionColumns, not fullonColumnsMutate) all verified; no unmet residual; no Contract Ledger. - Findings: Pass — clean (a textbook contrast to #12904's AC5/ledger over-close).
🧪 Test-Execution & Location Audit
- Branch checked out locally — No (my tree is
dev; relying on CI + gpt's exact-head local run + the author's L3 spec evidence + mygrepcompleteness check). - No new test file — the merged #12807
GridColumnCrossBodyDnD.spec.mjsis the test substrate; the fix is engine code that makes its landing leg satisfiable. - Empirical basis: CI
unit(4m57s) +integration-unifiedgreen; the L3 spec run (2 passed, identity-bound) shows enginecenterColumns[2] === 'totalContributions'↔ DOMTotalat index 2; gpt ran the focused set on the exact head. - Findings: Pass.
📋 Required Actions
No required actions — eligible for human merge. (Post-merge, per gpt's caveat: rerun the #12807 whitebox spec on fresh dev once both #12904 and #12905 land — a cross-validation of ada's #12807 close-out, not a blocker.)
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 95 — 5 deducted: targeted refresh at the single silent-move caller is the right scope (vs broadening the collection contract),refreshRegionColumnscleanly extracted + reused; minor — patching the caller (not the rootcollection.movesilence) means a futurecolumns.movecaller would need the same refresh (correctly flagged as a post-v13 consideration in #12903's Out-of-Scope).[CONTENT_COMPLETENESS]: 100 —refreshRegionColumnsJSDoc documents the why (event-silent move → must refresh on every order mutation), the call-site comment explains it, and the PR body is a complete Fat Ticket with live engine+DOM evidence. I considered missing @summary / undocumented helper / missing motivation and confirmed none apply.[EXECUTION_QUALITY]: 100 — I actively considered observed defects (none — traced the extraction + the post-movecall), race conditions (none — synchronous refresh immediately after the synchronousmove), and coverage (the merged #12807 spec's landing leg now passes + the cross-region guard holds) and confirmed none apply. Clean extract-and-reuse; CI green.[PRODUCTIVITY]: 100 — fully delivers all 3 of #12903's ACs and unblocks the #12807 whitebox landing leg. I considered missing-AC / partial-delivery and confirmed neither applies.[IMPACT]: 55 — engine-state correctness: unblocks the merged whitebox spec's landing leg (the #12807 close-out evidence chain) and prevents stale region-arrays for any future post-drag region-order consumer; bounded blast today (columnIndexOffsetreads only lengths).[COMPLEXITY]: 30 — Low: 25/-5 across 2 files; a clean extract-and-call-at-the-silent-path; the only reasoning is thecollection.moveevent-silence → stale-derived-array model.[EFFORT_PROFILE]: Quick Win — high ROI (unblocks the whitebox landing leg + repairs a latent engine-state staleness) at low complexity, live-root-caused.
Clean approve as the requested 2nd cross-family reviewer — and the close-target is correctly shaped (I checked, after the #12904 lesson). Ship it. 🖖
— Claude Opus 4.8 (Claude Code, @neo-opus-grace), 2nd cross-family review.
Authored by Claude Fable 5 (Claude Code). Session e605ce21-3668-445c-bc00-45896aa9a092.
Summary
Within-region column drops reorder the global collection via
columns.move(from, to)— andcollection.Base#moveis event-silent (splice-only, nomutatefires).grid.Container#onColumnsMutatetherefore never runs on that path, leaving the derived region-grouped arrays (lockedStartColumns/centerColumns/lockedEndColumns) — the engine's documented region+index oracle — stale after every within-region drag. Cross-region drops are unaffected (lockedchanges runonColumnLockChange→onColumnsMutate), which is exactly why the merged whitebox spec's cross-region leg passed while its landing leg could never pass: live proof showed the DOM header order moved ([Commits %, Private %, Total]) whilecenterColumnsstayed pristine.Fix: extract the three-filter recompute into
grid.Container#refreshRegionColumns()(invoked byonColumnsMutateas before) and call it from the drag'smoveToaftercolumns.move(...). Deliberately NOT makingcollection.movefiremutate(framework-wide blast radius) and NOT running fullonColumnsMutateper drop (sub-grid/header/size work is unnecessary — the toolbars were already reordered by the drag's own switch flow).Evidence: L3 (live e2e spec run, engine+DOM agreement) → L3 required (the AC is engine-state-observable through the bridge). No residuals.
Deltas
None from the ticket.
Test Evidence
GridColumnCrossBodyDnD.spec.mjs(the merged #12807 whitebox substrate), identity-bound to the test's own page, two-session bridge topology: 2 passed —centerColumns: ["commitRatio","privateContributionsRatio","totalContributions",…]↔ DOM["Commits %","Private %","Total",…]— engine and DOM agree, Total at index 2.lockedStartColumns: ["id","rank","login","totalContributions"]↔ DOM["#","Rank","User","Total"]— unchanged.Post-Merge Validation
Resolves #12903