LearnNewsExamplesServices
Frontmatter
id17823
titleThe live-resize readiness gate measures pixels, never frames
stateClosed
labels
enhancementaitestingperformancecore
assigneesneo-fable
createdAtAug 28, 2026, 9:30 AM
updatedAtAug 28, 2026, 3:42 PM
githubUrlhttps://github.com/neomjs/neo/issues/17823
authorneo-opus-vega
commentsCount1
parentIssue13158
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 28, 2026, 3:35 PM

The live-resize readiness gate measures pixels, never frames

Closed Backlog/active-chunk-18 enhancementaitestingperformancecore
neo-opus-vega
neo-opus-vega commented on Aug 28, 2026, 9:30 AM

Context

PR #17822 delivered Neo.component.Splitter#liveResize by moving pointer-frame geometry to the main thread: Neo.main.draggable.Resize mutates the registered target's outer layout node per frame, and the App Worker receives one frozen {axis, size, targetId} tuple at release for a single durable wrapperStyle commit.

The operator has since stated the driver for that design, and it is not an ownership boundary:

it was not really about ownership boundaries, but performance. if we want a live-resize, worker roundtrips in the shape app -> vdom -> main -> app can cost us lags and missing frames.

#17819 makes a specific promise about readiness: "the real Grid/TreeGrid witness decides whether the mode is ready beyond opt-in use." Under an ownership premise that is the right gate — correctness under heavy widgets is the question. Under the actual performance premise it is the wrong gate, because the witness measures geometry and never measures time.

Live latest-open sweep: checked the latest 20 open issues at 2026-08-28T07:28:12Z; no equivalent found. A2A claim sweep over the latest 30 messages found no overlapping [lane-claim]/[lane-intent]. Nearest neighbours are #9494 (main-thread scroll sync for Grid — same problem class, different surface) and #9486 (zero-jitter locked columns); neither covers splitter live-resize cadence.

The Problem

The eliminated round trip is the mechanism. Held frames are the outcome. Only the mechanism has a witness.

Verified at e5ebac2679, the state PR #17822 shipped:

Property Measured Where
Zero App-Worker move frames per pointer frame yes, positive-controlled test/playwright/unit/main/addon/DragDrop.spec.mjs — asserts width === '220px' before sent.toEqual([]), so the claim cannot pass with the code doing nothing
No geometry split-brain across buffered Grid + TreeGrid yes test/playwright/component/component/SplitterHeavyContent.spec.mjs — virtualized header/cell alignment, ≤3px return-to-boundary across four alternating real drags
Transient frames never become durable worker state yes test/playwright/e2e/core/SplitterLiveResizeNL.spec.mjs:114
Frame cadence, dropped frames, gesture latency no nothing

Every toBeLessThan* in both added specs is a pixel tolerance. There is no performance.now(), no requestAnimationFrame counting, no elapsed-time, duration, or budget assertion anywhere in the added coverage.

This is not a claim that live mode is slow. Removing an app → vdom → main → app hop per pointer frame is the single largest win available on this path, so the prior is strongly favourable. The defect is that the property the architecture was chosen for is the one property nobody can regress-detect, while three of its side effects are locked down.

The consequence is deferred, not present: liveResize defaults to false, so nothing ships on an unmeasured claim today. It becomes live the moment anyone proposes the default flip — or, sooner, when #17820 gives the dock a live preview whose entire value proposition is per-frame feedback on a surface where pane proportions are the workspace.

The Architectural Reality

  • The suppression point is src/main/addon/DragDrop.mjs#onDragMove: if ((!me.dragProxyElement && !resizeLocally) || me.alwaysFireDragMove) gates the DomEvents.sendMessageToApp forward. When a live resize is registered, resizeLocally is true and no move frame crosses the boundary.
  • Deferred mode reaches the same terminal math through Resize#apply with preview: false, so it computes size without touching the DOM and keeps its proxy presentation. Both modes are therefore driveable in one test run — this is the load-bearing fact for the fix below.
  • The fixture already exists. SplitterHeavyContent.spec.mjs drives four alternating real pointer drags across a buffered Grid and TreeGrid and already toggles between modes. No new harness, app, or example is required.
  • Neo.manager.VDomUpdate coalescing is what makes the worker path survivable at all; it is not what makes it fast enough for a per-frame gesture. That distinction is exactly what is unmeasured.

The Fix

  1. Extend test/playwright/component/component/SplitterHeavyContent.spec.mjs to sample frame timing across an existing heavy drag — not a new spec, not a new fixture app.
  2. Drive both liveResize: true and liveResize: false in the same run over the same gesture path, and assert the relative cadence relationship rather than an absolute millisecond budget.
  3. Establish the threshold by measurement first, then commit it as an assertion. Record the measured numbers in the PR body so the bar has provenance.
  4. Add the resulting gate to #17819's readiness language: the default flip requires the cadence assertion, not the geometry assertion.

Why relative, not absolute. An absolute wall-clock budget on shared CI runners is clock-brittle and will produce exactly the flake class that costs more triage than the coverage is worth. A same-run comparison self-normalizes against runner speed, and it simultaneously supplies the positive control: if the harness cannot distinguish live cadence from deferred cadence on the same machine, it cannot detect a regression in either, and a green result means nothing. The contrast is both the calibration and the falsifier.

Contract Ledger

Target Surface Source of Authority Proposed Behavior Fallback / Edge Case Docs Evidence
test/playwright/component/component/SplitterHeavyContent.spec.mjs (exists, added by PR #17822) This leaf under parent #13158 Gains frame-timing capture across an existing heavy alternating drag, for both modes in one run Timing capture unavailable in the runner → the test fails loud rather than skipping silently Spec-level comment naming why the comparison is relative The measured live-vs-deferred figures, recorded in the implementing PR
The cadence threshold Measurement taken in step 3, not authored in advance One committed assertion on the live-vs-deferred relationship A run where the two modes are indistinguishable fails the positive control and blocks the gate Spec comment carrying the measured baseline Same-run contrast, reproducible locally and in CI
#17819's readiness statement #17819 body, "the real Grid/TreeGrid witness decides whether the mode is ready beyond opt-in use" Amended so the readiness gate names the cadence assertion If #17819 has closed by then, the amendment lands on parent #13158 instead #17819 / #13158 body The assertion existing and being green

Decision Record impact

none — additive test coverage inside existing owners; no new module, no public surface, no ADR authority touched. It does inform D#17818's edge-resize contract, where the same frame-budget rationale currently appears as an authority ordering with correctness-shaped falsifiers only; that contract is Euclid's and is tracked there, not here.

Acceptance Criteria

  • SplitterHeavyContent.spec.mjs samples per-frame timing across at least one existing heavy alternating drag, with no new fixture app or spec file added.
  • The same run exercises liveResize: true and liveResize: false over the same gesture path.
  • The committed assertion is on the relative live-vs-deferred relationship; no absolute wall-clock millisecond budget is asserted in CI.
  • The positive control holds: a run in which the two modes are indistinguishable fails, and this is demonstrated by a deliberate inversion, not asserted in prose.
  • The measured figures behind the threshold are recorded in the implementing PR body, so the bar has provenance rather than an authored number.
  • Timing capture that is unavailable or returns no samples fails the test loudly; it must not skip, warn, or pass vacuously.
  • #17819's readiness language — or #13158's, if #17819 has closed — names the cadence assertion as the gate for changing the default.
  • The suite remains green across three consecutive CI runs at the implementing head, evidencing that the relative assertion is not clock-brittle.

Out of Scope

  • Changing liveResize to default true. This leaf builds the gate; it does not walk through it.
  • Neo.dashboard.DockSplitter live preview — that is #17820, and it should consume this gate rather than re-derive one.
  • The historical sibling min/max request #4723, still deliberately deferred per #17819.
  • A general-purpose performance harness, a benchmark dashboard, or any reporting artifact. The deliverable is one assertion with a threshold; a report nobody can regress against is the anti-pattern here.
  • Re-measuring the worker-roundtrip path as a design question. #17822 already settled the architecture; this measures the outcome it was chosen for.

Avoided Traps

  • A report-only benchmark. A number in a PR body decays the moment the next commit lands. Only a committed assertion detects a regression, which is why the deliverable is an assertion and not a measurement.
  • A cadence assertion with no contrast. Asserting that live mode holds some frame rate, alone, is unfalsifiable in practice — it passes on a fast runner regardless of whether the code is any good. The deferred-mode arm is not extra coverage; it is what makes the live arm mean something.
  • Inventing the threshold before measuring it. An authored number gets tuned until green. Measure, then commit the number, then let it fail.
  • An absolute wall-clock budget in CI. Shared runners make this flake, the flake gets muted, and a muted gate is worse than no gate because it reads as coverage.
  • Filing this as a diagnostic ticket. The temptation is a "measure and report" leaf. That produces no durable gate and would be correctly rejected as proof-only work.

Related

Parent: #13158 Related: #17819 Related: #17820 Related: #9494 Related: #9486

Origin Session ID: 460b7b3d-aef1-4ae5-a9d0-10a889cf06dd

Retrieval Hint: query_raw_memories("live resize frame cadence unmeasured mechanism versus outcome Splitter heavy witness")

Authored by Vega (Opus 5, Claude Code) 🌿