LearnNewsExamplesServices
Frontmatter
id12991
titleDelta capture API: unified stream capture with epoch windows
stateClosed
labels
enhancementaitestingarchitecture
assigneesneo-gpt
createdAtJun 12, 2026, 1:41 PM
updatedAtJun 12, 2026, 11:35 PM
githubUrlhttps://github.com/neomjs/neo/issues/12991
authorneo-fable-clio
commentsCount0
parentIssue12986
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[x] 12987 Delta-grammar kernel: op vocabulary + universal batch predicates
blocking[x] 13017 DeltaCapture: record windowId on applyDeltas-tap records
closedAtJun 12, 2026, 11:35 PM

Delta capture API: unified stream capture with epoch windows

Closed v13.1.0/archive-v13-1-0-chunk-1 enhancementaitestingarchitecture
neo-fable-clio
neo-fable-clio commented on Jun 12, 2026, 1:41 PM

Context

Second leaf of Epic #12986 (the vdom delta-stream contract). The #12942 census's spec-precedent sweep found the suite capturing deltas through three hand-rolled patterns, and the #12946 forensics hunt added a fourth (per @neo-fable's post-census scope note, DC_kwDODSospM4BB6cG). Every new delta-asserting spec re-invents capture plumbing; every forensics session re-pays the windowing cost by hand (~2h for the #12929/#12939 harvest vs ~5 lines under a capture kernel, per the cost inventory recorded in the census thread).

Release classification: post-release (contract-layer substrate; v13.0.0 shipped).

The Problem

The four patterns in the wild, each with different reach and different blind spots:

  1. Producer direct returnVdomHelper.update({vdom, vnode}) return value (Node-mode specs: Advanced, Calendar, Helper, table/Container, …). Precise, but only sees what the caller invokes — no component lifecycle, no batching context.
  2. Neo.applyDeltas interceptor — mock-replace the app-worker boundary fn and accumulate (RaceCondition, AsymmetricMerging, HiddenChildren, …). Sees real lifecycle traffic, but coarse: all components' deltas interleave with no windowing.
  3. Component method returnconst {deltas} = await component.set({...}) (button/Base, grid/Teleportation hybrid, grid/Pooling, …). Single-component focused; only works where the method returns deltas.
  4. Wrapped console at the apply boundary — monkey-patching console.log around Neo.config.logDeltaUpdates output (the #12946 hunt; also the original GridDeltaCapture harvest). Fragile, lossy, format-coupled — and the only pattern that sees the FINAL post-mutation batch on the Main side.

Cross-cutting gaps: no epoch windowing (per-gesture-phase delta attribution was hand-joined against trace timestamps), no layer naming (a captured batch doesn't say whether it is pre-send intent or post-mutation final truth — the #12942 OQ3 resolution requires capture instruments to name their layer), and no shared classification (each spec hand-rolls its op filtering; the kernel's vocabulary now exists for exactly this).

The Architectural Reality

  • The kernel (#12987, src/vdom/util/DeltaGrammar.mjs) provides the vocabulary + predicates this API classifies with — this leaf consumes, never duplicates it.
  • Sibling placement precedent: test/playwright/util/RmaHelpers.mjs is the existing shared test-utility home; the capture module lands beside it (structural pre-flight Stage-1 fast-path).
  • The two capture layers per the OQ3 two-layer split: VDom pre-send (attribution/intent — patterns 1/3 approximate it) and Main pre-apply / app-worker boundary (final-batch truth — patterns 2/4 approximate it). In the single-threaded unit env both layers collapse onto observable seams (VdomHelper.update return + Neo.applyDeltas), which is precisely why ONE facade can subsume all four patterns there.
  • The unit env's seams are stable, framework-owned functions — interception is deterministic (no console-format coupling, no timing races).

The Fix

New shared test utility test/playwright/util/DeltaCapture.mjs + spec test/playwright/unit/vdom/DeltaCapture.spec.mjs:

  1. One facade, explicit taps: createDeltaCapture({tap}) where tap ∈ {'applyDeltas', 'helperReturn'} — each captured batch is recorded with its tap name (the OQ3 layer-naming requirement), batch boundaries preserved (no flattening-by-default; the batch is the grammar's unit).
  2. Epoch windows: capture.epoch('drag-hold') → subsequent batches attribute to that label; capture.window('drop', async () => {...}) scopes an async action; reads like capture.deltasIn('drop'). Kills the hand-joined-timestamp workflow.
  3. Kernel-vocabulary classification: capture.opsIn('drop') returns effective-action counts via resolveAction (the implicit-updateNode spelling handled once, centrally); capture.findingsIn('drop', opts) runs validateBatch per captured batch — making every captured window grammar-checkable for free.
  4. Lifecycle hygiene: capture.restore() reinstates the tapped seam; double-install guards; teardown-safe under test failure (try/finally discipline documented in the JSDoc).
  5. The four legacy patterns get a migration note in the module JSDoc (behavior-level: which tap replaces which pattern) — migrating existing specs is explicitly out of scope (organic adoption; forced migration would churn 26 spec files in one PR).

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
test/playwright/util/DeltaCapture.mjs (NEW) Epic #12986; census capture-pattern inventory + DC_kwDODSospM4BB6cG fourth pattern Facade: tap selection, layer-named batch records, epoch windows, kernel classification, restore() n/a (new surface; nothing depends on it until specs adopt it) Module JSDoc incl. pattern-migration map Sibling RmaHelpers.mjs placement verified; 4-pattern inventory peer-verified in the census thread
Neo.applyDeltas (EXISTING — tapped, not modified) src/worker/App.mjs:119-125 (normalizes to array → promiseMessage) Tap wraps and forwards unchanged; capture is observational restore() reinstates the original fn n/a Signature verified during the census
VdomHelper.update() return (EXISTING — tapped, not modified) src/vdom/Helper.mjs:815-837 (returns {deltas, updateVdom, vnode}) Tap records the returned deltas array per call restore() n/a Return shape verified during the census
src/vdom/util/DeltaGrammar.mjs (EXISTING #12987 — consumed) Kernel exports (resolveAction, validateBatch) Imported for classification; never re-implemented n/a n/a Kernel PR #12988 (this leaf is blocked-by #12987)

Decision Record impact: none (test-substrate utility consuming the kernel; no runtime behavior change).

Acceptance Criteria

  • createDeltaCapture({tap: 'applyDeltas'}) captures real lifecycle batches with batch boundaries + tap name preserved; restore() reinstates the seam (verified by post-restore traffic NOT being captured).
  • tap: 'helperReturn' records VdomHelper.update() return batches equivalently.
  • Epoch windows: label attribution via both epoch() re-labeling and scoped async window(); deltasIn(label) returns only that window's batches.
  • Classification: opsIn(label) counts by effective action (action-less updateNode resolved correctly); findingsIn(label) surfaces validateBatch findings per batch.
  • Double-install guarded; teardown-safe (a throwing windowed action still restores).
  • Spec demonstrates each legacy pattern's use case reproduced through the facade (the four-pattern subsumption, asserted at behavior level).
  • Module JSDoc carries the pattern-migration map + the layer-naming rationale (self-contained, no tracking-ticket refs in durable comments).

Out of Scope

  • Migrating the 26 existing delta-asserting specs (organic adoption; forced churn rejected).
  • Browser-context / whitebox-e2e capture (the e2e variant rides the NL streaming leaf, OQ4) and the Main-thread DeltaUpdates pre-apply event tap (real-browser-only; meaningless in the single-threaded unit env where applyDeltas IS the boundary).
  • Replay/fixture persistence (OQ5 leaf — versioning story first).
  • Signature shape-class assertion helpers (OQ2 leaf — they will CONSUME this capture API).
  • Any change to the tapped seams' behavior.

Avoided Traps

  • Flattening batches by default: the batch is the grammar's unit (ordering rules are per-batch); a flattened stream destroys exactly what validateBatch checks. Flatten only as an explicit accessor.
  • Console-format coupling (pattern 4's fragility): tap framework seams, never log output.
  • Auto-migrating existing specs in the same PR: 26-file churn bundled with new substrate = unreviewable; the #12941 precedent shows delta-spec churn rots fast.
  • Re-implementing classification: the kernel owns the vocabulary; this module imports it (the epic's one-vocabulary constraint).

Related

Epic #12986 (parent). Blocked-by #12987 (imports the kernel). Source: #12942 census + DC_kwDODSospM4BB6cG (fourth pattern + epoch-marker inventory). Adjacent: #12940 (logDeltaUpdates docs — the manual lever this eventually supersedes for unit work).

Live latest-open sweep: checked latest 15 open issues at 2026-06-12T11:41Z; no equivalent found. A2A in-flight sweep: clean (@neo-gpt on #12989 + reviews; capture decomposition announced as mine in the graduation + leaf-1 broadcasts).

Origin Session ID: 3e1566f6-6336-4db4-8726-189004578d8b

Retrieval Hint: "delta capture API epoch windows tap applyDeltas helperReturn four patterns DeltaCapture"