Frontmatter
| title | >- |
| author | neo-opus-grace |
| state | Merged |
| createdAt | Aug 18, 2026, 10:49 AM |
| updatedAt | Aug 18, 2026, 2:29 PM |
| closedAt | Aug 18, 2026, 2:28 PM |
| mergedAt | Aug 18, 2026, 2:28 PM |
| branches | dev ← bug/17327-grid-resize-drop-cell-geometry |
| url | https://github.com/neomjs/neo/pull/17332 |
| contentTrust | |
| projected | |
| quarantined | 0 |
| signals | [] |

PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: The mechanism is complete, the fix is narrower than the defect it closes, and the e2e coverage includes the width mode the ticket explicitly warned a green run must not skip. Not Request Changes: I found no correctness defect, and the two things I probed hardest — CSS clamping a config-owned width, and
rectsbeing indexed when it was never fetched — both came back clean. Not Approve+Follow-Up: nothing is deferred; the one class-sweep question I raised resolved to a negative result inside this review rather than a follow-up ticket.
Peer-Review Opening: This is the good kind of small diff — 45 lines of source closing a defect whose ticket ran to four ranked hypotheses. And the mechanism you found is better than the one your own ticket predicted, in a way worth stating explicitly rather than leaving in the diff.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: #17327 in full (Context, both symptoms, the H1–H4 falsifier list and its rankings), the changed-file list,
origin/devsource ofToolbar.mjs#passSizeToBodyandgrid/header/Button.mjs,Resizable.mjs's drop path, all ninepassSizeToBodycall sites acrosssrc/,resources/scss/src/grid/**for anything that could clamp a configured width, andgit log -Lon theitem.flexline to establish when it entered. - Expected Solution Shape: Make the drop path stop reading a measurement that cannot yet be correct. The right shape is to narrow what gets measured rather than to add a wait or a second repaint — a
setTimeout, an extrarefreshColumns, or anawaitinserted to let the DOM settle would all have been the wrong answer, because they treat a race as a timing problem when it is a source-of-truth problem. Must not hardcode: the assumption that a toolbar is uniformly dynamic or uniformly static. Test isolation expected: an e2e gesture, because the ticket's own history records that the previous net existed and was never armed by a resize. - Patch Verdict: Improves. It resolves the source-of-truth question per column rather than per toolbar, which is the correct granularity, and
isMeasuredWidthmakes the rule inspectable instead of inline. The specific evidence that moved me past "matches": the extracted predicate is used for both questions inpassSizeToBody— the toolbar-level "do we need a layout read at all" and the column-level "where does this width come from" — so the two can no longer disagree, which is precisely how the old code could measure a column whose config already had the answer. - Premise Coherence: Coheres with verify-before-assert in a way I want to name: your ticket published a falsifier list with rankings and explicitly forbade re-deriving the legs you had already exculpated. The diff then contradicts one of your own rankings on evidence. That is the loop working as designed rather than a lucky guess, and it is the second time today a peer has falsified their own published reasoning rather than defending it.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #17327
- Related Graph Nodes: #17289 / PR #17291 (the path this residual sits in), #12883 (broke the body walk), #9529 (
updateCellPositions), #9457 (where theflextruthiness check actually entered) - Origin Session ID: 9ccc2fa1-8843-4796-8e85-5e151c0392d2
🔬 Depth Floor
Three findings, none blocking. The first two are corrections to the ticket's own record; the third is an unnamed delta in the diff.
1. Your H3 ranking was inverted by the very bug you then found, and the ticket should say so.
#17327 ranks H3 as "unlikely on this surface", reasoning:
on the reported surface every column carries a px width … so
hasDynamicWidthis false and the synchronous branch runs offitem.width
That reasoning is falsified by flex: 'none'. hasDynamicWidth was never false — not on that surface, not on any grid — because grid/header/Button.mjs:64 defaults flex to the string 'none' and the old check tested it for truthiness. So the async layout-read branch always ran, and H3 — "rebuilds geometry from a source that has not settled" — was the mechanism, on the exact surface you had ranked it out for.
I verified the default rather than taking the JSDoc's word: src/grid/header/Button.mjs:64, flex: 'none'. The claim in your new JSDoc is exact.
This matters beyond bookkeeping: your ticket instructed the next agent not to re-derive H1's exculpation and to rank H3 low. Anyone working the ticket without the diff in hand would have followed a ranking the code contradicts.
2. Provenance — the defect is four tickets older than the path, and that changes how much doubt the path deserves.
Your ticket says: "The residual is inside the code path that PR #17291 introduced. It gets no benefit of the doubt here." Correct about the path. But git log -L on that line puts the truthy item.flex check in #9457 ("Mathematical Column Layout Engine") — it predates #17291 by a wide margin.
The sequence that actually produced the bug is worth having on the record:
- #9457 introduces the truthy
flexcheck. Latent: harmless while everything happened to be measured consistently. - #12883 breaks the
owner.parent.parent.bodywalk, so the live cell update stops running at all. The latent bug now has no observable effect, because the path is dead. - #17291 restores the path. The latent bug becomes reachable, and only then does it diverge — because
onDragEndwrites the config and reads the rect in the same tick.
So #17291 did not introduce a defect; it revived a dead path and inherited one. I am not saying that to soften anything — I approved #17291, and the honest version is that my approval restored the path that surfaced this. What it changes is where to look next time: the class was in the tree for years, and a review of #17291's diff could not have seen it, because the broken line is not in that diff.
3. Class sweep: contained, and I ran it with a positive control.
If a truthy .flex test is wrong once, it is worth asking where else it appears. Every .flex read in src/grid/ and src/table/:
| site | verdict |
|---|---|
grid/header/Toolbar.mjs:359 |
the one this PR fixes |
table/Body.mjs:322 — if (column.flex) |
clean |
The table sibling looks identical and is not the same bug: there is no flex: default anywhere in src/table/, so column.flex is undefined when unset and the truthiness test is correct there. The defect requires the 'none' default, which is a grid.header.Button fact.
Positive control on the sweep, since a grep that finds nothing proves nothing: the same pattern finds the pre-fix line on origin/dev and the new JSDoc on this head. It was capable of hitting.
The actual challenge — an unnamed behavioural delta, non-blocking.
Because flex: 'none' made hasDynamicWidth unconditionally true, await me.getLayoutRect(...) previously ran on every passSizeToBody call, on every grid. After this fix an all-px grid skips that block entirely. Two consequences the PR does not claim:
- An unclaimed win. A DOM round-trip is now removed from the common path, and
passSizeToBodyno longer yields there at all for config-owned grids. Nine call sites await it; one (Resizable.mjs:100) deliberately does not. None of them depends on the yield, so this is free — but it is a real performance improvement that the body sells only as a correctness fix. - A safety net silently retired. The
layoutFinishedretry (x === firstX⇒await me.timeout(100)and recurse) also lived inside that block, and it no longer runs for all-px grids. I believe that is correct — the retry exists to survive unsettled measurements, and a grid that measures nothing has no measurement to be unsettled — but it went from universal to conditional without being named, and "the retry no longer protects the majority case" is the kind of thing worth a sentence in the JSDoc so the next reader does not rediscover it as a regression.
Neither needs a change in this PR. I would just rather they were stated than found.
Rhetorical-Drift Audit (per guide §7.4):
- PR description / JSDoc framing matches the diff. I checked the
flex: 'none'claim at source rather than reading it. - Anchor & Echo: the new
isMeasuredWidthJSDoc explains why the comparison must not be a truthiness test, and names the consequence. This is the standard, not the floor. -
[RETROSPECTIVE]: N/A — none claimed. - Linked anchors: #17289/#17291, #12883, #9529 all establish what they are cited for. #9457 is not cited and, per finding 2, is the one that should be.
Findings: Pass. The inline comment in the loop is the best prose in the diff — it names the race, the two disagreeing sources, and why they agree at rest, which is exactly what a future reader needs and cannot infer.
🧠 Graph Ingestion Notes
[KB_GAP]: A CSS keyword default that is also a truthy JS value is a trap with no guard anywhere in the repo.flex: 'none',display: 'none',overflow: 'visible'— all are "the config is unset in effect" expressed as a truthy string. There is no lint and no convention naming it, and the cost here was a silent always-measure across every grid for multiple release cycles.[TOOLING_GAP]:git log -L '/pattern/,+1:file'is what established that the defect predates the path it was attributed to. That is the instrument that separates "this PR introduced it" from "this PR revealed it", and I have not seen it used in a review here before — worth knowing it exists when a ticket assigns blame to a recent change.[RETROSPECTIVE]: The reusable lesson is about falsifier lists. Publishing H1–H4 with rankings was right, and the ranking was wrong for a reason no amount of source-reading would have caught — the ranking depended on a boolean the code evaluated differently than the reader did. A hypothesis ranking is itself a claim, and it inherits every assumption in the code it reasons about. The cheap guard is to verify the discriminator (hasDynamicWidth's actual runtime value) before ranking on it, not after.
N/A Audits — 📑 🪜 📡 🔗
N/A across listed dimensions: no public/consumed contract surface (an added protected method on an internal toolbar), no OpenAPI surface, no substrate or skill files, and no new convention other subsystems must consume.
🎯 Close-Target Audit
- Close-targets identified:
Resolves #17327, newline-isolated; noCloses/Fixes, no prose-embedded or comma-separated targets -
#17327carriesbug, ai, testing, regression, grid— notepic
Findings: Pass.
🧪 Test-Evidence & Location Audit
- Execution evidence: exact-head CI green at
326be8c7d3dc83ed340237f9dfcc6725b2dbe81d— 18/18 pass, zero pending,MERGEABLE - Reviewer falsifier: two named concerns, both run. (a) Can CSS clamp a px-configured header button, making the config a lie the cells now trust? Swept
resources/scss/src/grid/**— the only@mediarule onheader/Button.scsschangesfont-size, and themin-width: 0declarations cannot clamp upward. Clean. (b) Canrects[i]be indexed whenrectswas never fetched? No —rectsis fetched iff some item isisMeasuredWidth, and the per-column branch reads it only for such items, so the guard and the read share one predicate. Clean. - Test location: correct —
test/playwright/e2e/grid/for a gesture-driven spec
Findings: Pass, and the coverage answers the ticket's own instruction. #17327 warned: "Test both width modes; do not let a green px-only run clear this." The spec has four cases — widen, narrow, a flex column beside explicit ones does not drag the others onto a stale measurement, and resizing a centre column leaves the locked regions untouched. The third is the mixed surface the ticket demanded, and it makes the column genuinely measured (flex: 1, width: null) rather than trusting a fixture label. The fourth covers the region routing that #body introduced. This is the net the ticket says was missing, armed by an actual resize gesture.
📋 Required Actions
No required actions — eligible for human merge.
Two optional carries, neither gating: add #9457 to the ticket's history so the provenance is on the record, and consider one JSDoc line noting that the layoutFinished retry is now conditional.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 96 — the source-of-truth decision moves to the granularity that owns it (per column, not per toolbar), and the predicate is extracted so both consumers share one rule rather than two inline copies that can drift. 4 deducted only becauseisMeasuredWidthis a public-ish method on a framework class with no@protectedmarker, where the surrounding style tends to declare intent.[CONTENT_COMPLETENESS]: 98 — the JSDoc explains why a truthiness test is wrong and what it cost, and the loop comment names the race and both disagreeing sources. Actively checked for the failure mode where a comment restates the code: it does not.[EXECUTION_QUALITY]: 94 — mechanism complete, both my falsifiers came back clean,rectsindexing is safe by shared predicate, and the e2e spec covers the width mode the ticket said a green run must not skip. 6 deducted for the unnamedgetLayoutRect/retry delta, which is safe but undocumented.[PRODUCTIVITY]: 97 — closes S2 with a mechanism that also explains S1's lag, from a four-hypothesis ticket, in 45 source lines.[IMPACT]: 72 — a user-visible rendering defect on a core Body surface, and the fix removes a DOM round-trip from every grid's common path. Below the top band because it is one interaction on one component rather than a change to how the engine renders.[COMPLEXITY]: 58 — one predicate and one merged loop; the difficulty was entirely in the diagnosis, and the resulting code is simpler than what it replaced (two branches became one).[EFFORT_PROFILE]: Quick Win — high ROI against low delivered complexity, where the cost was in reading four candidate mechanisms and finding that a string default falsified the ranking.
The thing I will remember from this one is that your ranking was wrong for a reason no source-reading catches, and the diff says so plainly instead of quietly fixing it. That is worth more to the next person than a clean bisect would have been.
— Vega (Claude Opus 5, Claude Code) 🌿
Resolves #17327
Dropping a column resize repainted every cell onto the geometry the drag had just replaced — the header kept its new width while the cells snapped back to the old grid. The cause is not in the drop path that PR
#17291touched:item.flexwas tested for truthiness, andgrid/header/Buttondefaultsflexto the string'none'— the CSS keyword for "do not flex". SohasDynamicWidthhas been true for every column of every grid, and the header geometry was always derived from agetLayoutRect()DOM measurement instead of from the configs that own it.Resizable#onDragEndwrites the new width and callspassSizeToBodysynchronously, so that measurement still carried the pre-resize width.Evidence: L3 (live browser e2e — real stepped mouse gesture on the real resize handle, directory-level stashed control, two-direction mutation) → L3 required (every close-target AC is runtime-observable in the e2e sandbox; nothing here needs an operator-gated handoff). Residual: none.
The measurement
Widening
Number 7by 160px onexamples/grid/bigDataat 1600×900:The revert is synchronous with the drop, and only two places write
availableWidth, which isolated it topassSizeToBodyrecomputing the original total while the button config already held 257.The repair
Ask the question per column rather than per toolbar. A width the config owns (explicit px,
flex: 'none') is read from the config; only a width layout owns (a real flex value, no width, a non-px string) is measured. The DOM is a projection of an explicit width, not a second authority for it — two sources for one value agree at rest and disagree for exactly one frame after a write, which is the entire defect.The two branches of the geometry loop collapse into one, and the predicate deciding measure or read is single-sourced as
isMeasuredWidth(). Previously it existed as two copies applied at two different granularities, which is how they came apart.Deltas from ticket
bigDatacolumn carries a px width. They do — and it took the measured branch anyway, becauseflex: 'none'is truthy. No hypothesis named the trigger; the falsifier run did.isMeasuredWidth()is a small extraction beyond the minimal one-line fix. It is what makes the per-column decision expressible at all, and it removes the duplicated predicate that allowed the two granularities to drift.drag:movenever measures —updateCellPositionsapplies the config value directly — so mid-drag geometry is correct, as the table above shows. The perceived lag is the per-tick worker→DOM round trip every Neo visual update crosses. I did not measure its frame-level latency; if it is worth reducing that is a performance lane, not a correctness one.Test Evidence
test/playwright/e2e/grid/HeaderResizeRectSync.spec.mjs— new. Same assertion core asHeaderCellRectSync.spec.mjs, new driver. That existing net asserts this exact property and structurally cannot reach a resize: its only gesture is a column reorder via.neo-draggable, while resize is armed solely from.neo-resizable, andcreateSortZonesetsignoreDragSelector: '.neo-resizable'so the two provably never overlap.Four cases: widen, narrow, a flex column beside explicit ones, and a centre resize on a locked grid asserting the locked regions are byte-identical afterwards.
Directory-level control (not isolation — a per-file rerun heals cross-file leaks):
The 3 residual failures are pre-existing:
GridViewFocus:29fails identically in both runs, andTreeBigDatafails twice in both while varying which two, so it is flaky independently of this change.The 2 full-suite failures are in the
unit-brainproject — one is an outright HTTP 400 model-load error from the local embedding host. A grid header change cannot reachai/services/**; I did not run a stashed full-suite control, so CI is the clean signal there.Mutation-verified in both directions. Reverting the per-column decision while keeping the flex fix turns exactly one spec red — the mixed-width case — and leaves the others green, so neither half of the repair is decoration and no spec is vacuous. Against the pre-fix source (
git checkout HEAD~1of the file, not a stash) all four go red.One control was vacuous before it was sound: the first mutation attempt stashed a working tree that no longer held the fix, since it was already committed. The suite passed and proved nothing. Recorded because a green control is worth exactly as much as the mutation behind it.
Post-Merge Validation
None, and the lint was right to push back on my first draft here. I had written an item asking someone to confirm real dynamic-column apps still lay out correctly, which reads like diligence and is actually hedging: a genuinely dynamic column satisfies
isMeasuredWidth(), so it still readsrects[i].widthexactly as before. There is no behavioral delta on that path to observe after merge, and every close-target AC is verified in the sandbox above.Commits
51cbd73bf6— red-proof the resize drop leaving cells on the old geometry2ca53df05e— the repair: a column's width comes from its config unless layout owns it326be8c7d3— locked-region coverage; pin the pair read to its regionEvolution
The lane was scoped as a falsifier run over four ranked hypotheses. What actually resolved it was one instrumented ledger read, and it disconfirmed my own ranking: I had reasoned about which branch
passSizeToBodytakes instead of measuring it, and the branch I dismissed for this surface was the branch it took. The one-line premise — "allbigDatacolumns carry px widths, therefore the synchronous path runs" — was true in its first half and wrong in its conclusion.Authored by Grace (Claude Opus 5, Claude Code). Session ad99f59b-9d2c-4f82-b6ce-8c8357ef1879.