EP-0033: re-frame.ui View Evidence and Debugging¶
Status: accepted Type: standards-track Created: 2026-07-16 Resolution: accepted 2026-07-11
Abstract¶
Dev builds of the compiled view substrate are a glass cockpit: per-view compiler
manifests describe every slot, site, and capability before mount; per-commit
instance records carry a :rf.view/causes vector, occurrence-level identity,
and loss-accounted histories. Attribution is emitted at the cause site, never
reconstructed — no Fiber walking, no DOM scraping, no cloneElement tagging.
One versioned evidence schema is shared by Xray, Story, Pair, compiler
diagnostics, and tests; production builds contain none of it, provably.
The primary normative home is spec/004D-Freehand-Compiled-Grammar.md
§View identity and the instrumentation surface; per EP-0009, where this EP and
the spec differ, the spec governs. Every runtime-tier evidence schema, trace
op, and error/warning id this design names gets a catalogue row in
spec/009-Instrumentation.md — the
one-catalogue rule, scoped to the runtime tier: compile-tier :rf.ui.compile/*
ids live in the maintained compiler roster and deliberately get no Spec 009
rows.
Motivation¶
Legacy adapters reconstruct render attribution after the fact (post-render walks, cloneElement tagging) — imprecise under concurrent rendering, identity-conflating, and handing each tool a subtly different truth. The compiler already knows every slot, site, and interop boundary; the runtime knows which committed commit each observation belongs to. The unresolved alternatives — where evidence is emitted, which identities exist, how tools consume them, what production keeps — are contract decisions warranting a durable record, not a bead.
Specification¶
Scope. This EP fixes one versioned evidence schema consumed by Xray, Story,
Pair, diagnostics, and tests; evidence usable before mount (the manifest) and
truthful after commit (the instance record — speculative renders publish
nothing); honest bounds everywhere (loss accounting, :owned? false overrides,
qualified retroactive lifecycle annotations); and production erasure as a proof
(G-7/G-11). It does not add new Xray panels (mounted-views, SSR/roots, and
heatmap panels come only after an information-architecture review — the v1 emit
obligation is the schema, not panels); no render flamegraphs or per-view
timeline scrubbers (React Performance Tracks correlate via render-key/epoch
ids); no second runtime model for Story/Pair, no remote mutation API, no
general query engine over evidence; and no production telemetry — the always-on
error channel is EP-0008's.
Shipped surface and specified extensions¶
The evidence tier shipped with the S3 conformance slice, and the certified
surface (spec/conformance/S3-view-conformance-profile.md) is a bounded subset
of the schema this EP specifies.
Shipped: the five re-frame.ui.tool projections; occurrence-shaped
instance records (:occurrence ordinal + :view-id + :root-id +
:connection + :lifecycle intervals + bounded render evidence) with explicit
loss accounting; and a bounded render-cause set — #{:subscription :hmr
:disposed}. Host-state writers (local) are proven through G-15's writer
composition and render count, not a :local-state cause row — that render
cause is specified for S6, not shipped at S3.
Shipped at S6 (rf2-vxgfnd.98.1 — see §S6 view-evidence delta below): the
integer :render-key and :generation record fields, :root-id, per-observation
:observations, and the per-commit :rf.view/causes vector with its shipped
six-kind roster (:mount, :subscription with version from→to, :story-override,
:local-state, :hmr, :disposed) plus the :foreign-or-react honesty fallback;
the data-rf2-source-coord + render-key DOM annotation on compiler-owned host
roots; and the Spec 009 evidence-schema rows.
Deliberately deferred with named triggers (never a silent drop):
:parent-render-key, the singular keyword :frame-id (frame attribution is
per-observation instead), and the :prop / :frame-:context / :resource /
:hydration-correction / :reconnect-correction / :epoch-restore / :hmr-remount
cause kinds. Consumers still tolerate those absences explicitly rather than
fabricating them (tools/xray/spec/021 §3.4.1: no :epoch-restore / :hmr-remount
evidence lands, and Xray invents neither). The dated delta below records exactly
what shipped and enumerates each deferral's trigger; the Spec 004 §View identity
amendment is its normative home. The schema originally sketched here is the ruled
design intent.
S6 view-evidence delta — shipped (2026-07-21, rf2-vxgfnd.98.1)¶
The §Two evidence layers / §Render causes design below is the ruled intent; this
dated delta records what the S6 slice (rf2-vxgfnd.98.1, slices a–d) ACTUALLY
shipped and where it deliberately trims or defers under the delta protocol ("trim
deliberately, never silently drop"). It records the shipped surface; the parallel
spec/004-Views.md §View identity amendment is the normative home.
Record shape (Ruling 1 — Option 1A, honest capture). The DEBUG-only per-commit
committed-instance record, minted at each connected commit and read via
re-frame.ui.reactive/commit-record:
{:render-key 1042 ; module-global monotonic integer, minted fresh per connected commit
:view-id :cart/row ; the cell's authoring identity
:generation 3 ; the committed view-body generation
:root-id :page/shop ; owning root incarnation (nil under no root — Tier-1/JVM)
:connection :connected ; the certified three-state model, at a connected commit
:observations [{:kind :subscription :query [:cart/item 17]
:target-id 88 :version 12 :owned? true :frame-id :shop}]
:rf.view/causes [{:cause :subscription :target 88 :query [:cart/item 17]
:frame-id :shop :from 11 :to 12 :epoch 4}]}
:parent-render-keyis DEFERRED out of the record — trigger: "an Xray IA review designs a hierarchy-consuming surface". No parent-capture machinery is built in S6 (notools/xray/specconsumer reads view parentage; capture would be decoration).- Frame attribution is PER-OBSERVATION, not a singular
:frame-id. Each observation carries its own target's:frame-id; a cell observes a SET of frames (cell-framesis a set by design — an override-only cell observes none), so the record carries NO singular top-level:frame-id. This amends the singular:frame-idsketched in §Two evidence layers. - Speculative renders publish nothing (I-1/I-2); production mints no record and no
:render-key— the whole plane is elided (G-7/G-11).
Causes roster (Ruling 2, as reworked — a deliberate 6-ships / 7-defers trim).
The per-commit :rf.view/causes vector ships six kinds plus the honesty
fallback, each projected from an existing signal at its cause site (never a new
capture machine, never a general evidence framework):
:mount— the first connected commit (:fresh→:connected); a bare{:cause :mount}.:subscription— a value-movement port note carrying its ruled detail{:target :query :frame-id :from :to :epoch}(coalesced: earliest:from, latest:to). The port note itself carries the:subscriptionkeyword (unified port-wide from the original:value— rf2-ao46i), so the record adopts it unchanged.:story-override— a (re)acquired static Story-override target, carrying{:override-id :version}.:local-state— the substrate-owned local writer bridge (gated on anObject.iscommitted change); a bare marker.:hmr— a real registrar-replacement fan-out; a bare marker.:disposed— a:disposedport note; a bare marker.:disposedis a ROSTER ADDITION — it ships today from the port but was absent from the specified roster; recorded here so the trim protocol does not orphan a real cause.:foreign-or-react— the honesty fallback for a commit whose pending evidence window is empty.
Deferred, as deferred-with-triggers rows (consumers keep tolerating absence
exactly as tools/xray/spec/021 §3.4.1 does; nothing is emitted for these):
:prop— trigger: an IA review designs a per-commit cause-detail surface.:frame/:context— trigger: the substrate gains context-driven re-render visibility (until then a context re-render falls into:foreign-or-react).:resource— trigger: a resource-attribution question from dogfooding.:hydration-correction— trigger: EP-0034's hydration probes land.:reconnect-correction— trigger: hidden-reveal repaints confuse in dogfooding (until then:subscriptionstays honest for a value that moved while hidden).:epoch-restore— trigger: an Xray or Pair debugging surface is designed that needs restore-driven commits distinguishable from ordinary:subscriptionmovement, naming the consumer that will read the restore attribution (restore token + target epoch). Ruled option (c): restore provenance is outsideperform-restore!'s dynamic extent and headless hosts have no value-movement watch, so honest emission needs a cross-artefact restore-provenance channel no consumer reads; until then a restore-driven movement surfaces honestly as:subscription(watchable hosts) or:foreign-or-react(headless).:hmr-remount— trigger: a substrate remount-pairing channel tying a mount to its predecessor's teardown. Deferred because a remounted instance is React-indistinguishable from a fresh mount at the same view-global generation using only the view-global remount counter; honest per-instance attribution needs a teardown→remount pairing signal (cross-surface + a fiddly closing rule).
Schema version + vocabulary. The record + roster ship under
re-frame.ui.tool/schema-version 3 (bumped from 2 in this same change; Xray,
Story, and Pair move in lockstep so none degrades to []). The :value→
:subscription rename first landed on the per-commit record surface; the port-wide
unification (rf2-ao46i, RULING 2) then carried it to the observation port keyword
and Xray's cumulative explain-render window, so the whole substrate now speaks
the one #{:subscription :hmr :disposed} vocabulary — no two-vocabulary residue.
Two evidence layers¶
Compiler manifest — what can happen. Per view, dev-only: source coords,
prop slots + schema, template fingerprint, hook signature, capability bits, and
every site — subs with query shapes; events with event shapes and a
:serializable?/:dynamic flag; effects; presence sites;
trusted-markup ui/html sites; render-slot sites (P0-3) — each with source +
template path. No runtime values; useful before mount, to Xray, Story, editors,
and agents. Per the readiness package (EP-0035, P1-5), the dev/test projection
of this manifest is a versioned public shape additionally carrying per-prop
docs/defaults and declared slot metadata — one source of truth replacing
parallel args-description systems (re-com maintains ~57 such Vars).
Committed instance record — what did happen. Published only at connected commit; speculative renders publish nothing. Shape (versioned):
{:render-key 1042 :parent-render-key 1039
:root-id :page/shop :frame-id :shop :view-id :cart/row
:generation 3 :connection :connected
:observations [{:kind :subscription :query [:cart/item 17]
:target-id 88 :version 12 :owned? true}]
:rf.view/causes [{:kind :subscription :target-id 88 :from 11 :to 12}]}
parent-render-key gives direct hierarchy — no Fiber or DOM walking (legacy
adapters keep their fallback; this substrate never needs it). Keyed repetitions
inside one instance carry occurrence-path (e.g. [{:site 17 :key
order-id}]) on DOM annotations, event provenance, and presence records —
render-key alone is insufficient. Story overrides display honestly: {:kind
:story-override … :owned? false} — a visual override, never evidence a
subscription computed it.
Connection is recorded as observed: the runtime emits exactly three states
— :connected / :disconnected / :dead — and the immediate cleanup fact is
:disconnected {:reason :unknown} (public React cannot distinguish an Activity
hide from an unmount at cleanup). :activity-hidden and :unmounted are
qualified retroactive annotations of the prior interval, never runtime
states: a reconnect proves a hide; host/root teardown proves unmount; GC-based
inference (if enabled) is best-effort/eventual — no timestamp, a bounded
tombstone that never retains the cell. Records distinguish runtime state vs
tool label vs inference; transitions never fabricate renders.
Five identities, never conflated¶
root-id (Spec 011), frame-id (Spec 002), render-key (one committed view instance — Spec 004), occurrence-path (a keyed repetition inside one instance — Spec 004), observation-target (Spec 006). Sites get compile-time indexes + source anchors; identity under HMR is source anchor + structural path + generation, released/remounted on ambiguity.
Render causes¶
:rf.view/causes is a vector — a commit can have several causes; headers
summarize ("3 dependencies changed in ::refresh-complete"). The roster:
:mount · :subscription (target, query, version from→to, epoch, upstream
event/sub) · :story-override · :prop (changed top-level slots — the
sound cheap promise; nested paths are a dev diff view on demand, never a
default emit) · :local-state · :frame / :context · :resource · :hmr /
:hmr-remount · :hydration-correction · :reconnect-correction ·
:epoch-restore (the restore operation token + target epoch — the repaint
is caused by the restore operation; old epoch records are never rewritten or
back-filled) · :foreign-or-react (the honest fallback — never fabricate
precision).
Loss accounting: every bounded buffer reports total / retained /
dropped; counts are labeled exact only when dropped = 0 — no silent
truncation presenting as completeness.
The static interaction surface¶
Handlers are data: the inspector shows any element's event vector before it is
clicked, plus the registered handler's source/schema; unregistered ids warn at
render. Sites classified :dynamic say so — the static surface covers literal
and normalized-branch sites and is honest about the rest. Dev warning families
(unregistered ids, render-phase dispatch, cross-frame carried ops, suppressible
accessibility checks) land as catalogue rows on the issue surfaces.
Source ↔ DOM ↔ cause navigation¶
Compile-time data-rf2-source-coord + render-key (+ occurrence-path)
annotation on compiler-owned host roots — today's attribute vocabulary, so
existing Xray click-to-source works day one. Chains both directions: app-db
path → subs → views → elements, and element → event → handler → effects → state
diff → sub cascade → commits. React DevTools stays independent; Performance
Tracks correlate via render-key/epoch ids. Xray answers "why does this view
carry a client runtime?" from capability bits + sites — the absence story made
inspectable.
Tool integration posture¶
- Xray — enrich existing surfaces first. The Views tab gains the causes
vector, occurrence identity, and the three-layer lifecycle grammar; dispatch
surfaces gain event-site provenance;
:epoch-restorerenders where causes render; warning families ride the issue surfaces. New panels come only after an IA review shows an existing surface can't answer the question. Every trace shape is a Spec 009 catalogue row first; every Xray change updatestools/xray/spec/*in the same change. - Story: mounts scenes by view id; asserts on JVM structural trees +
app-db; sub-overrides via the observation-target protocol with
:owned? falsehonesty; JVM override injection is the explicitui.test/renderoption — one mechanism per host, both named. - Pair: hot-swap rides the existing nREPL HMR path; the five frozen
read-only projections (
view-manifest,mounted-views,explain-render,view-dependencies,view-event-sites) live inre-frame.ui.tool— the tool tier, never the authoring namespace. - Epochs: restore causes carry the operation token — the timeline stays truthful through time travel; history is never rewritten.
Privacy and production erasure¶
Manifests carry shapes and source, never live values; render values classify
through their owning sub/schema; sensitive/large values redact per EP-0025
before off-box egress; histories are bounded with loss accounting; paths are
project-relative. Occurrence keys, override values, and event payloads route
through the one egress-projection policy (EP-0025). Sub query vectors
deliberately do not route through the classification egress chokepoint:
they are identity — the sub-cache key, the skip-dedup key, reactive-graph edge
endpoints — and egress raw on every query-vector-bearing slot as an accepted,
documented fail-open (the positional-event-args precedent); only the Spec 010
schema-axis backstop whole-slot scrubs them, for :sensitive?-schema'd subs.
Erasure is a proof: compile-time defines + the G-7/G-11 bundle-scan gates
put manifests, cause vectors, histories, warning text, and data-rf2-* strings
on the scanned absence roster; the always-on Spec 009 error contracts (EP-0008)
remain.
Guide impact¶
The substrate guide's debugging chapter teaches the glass cockpit; docs/core
inherits it at promotion. No other human-facing guide change yet.
Rationale¶
Emit-at-cause-site over reconstruction is the load-bearing choice:
reconstruction made legacy attribution fragile and silently degrades under
concurrent rendering. Two layers keep the manifest useful before mount and the
record truthful after it; the five-identity split prevents the conflations that
made "which one re-rendered?" unanswerable. The honesty rules (:owned?
false, bounded :prop precision, :disconnected {:reason :unknown}, loss
accounting) trade apparent precision for provable truth — a tool that
fabricates certainty is worse than one that says "unknown". Enrich-first keeps
Xray's hardest-won asset — its information architecture — from dilution by
panels bought before demand.
Backwards Compatibility¶
Pre-alpha; no shims. The legacy [view-id instance-token] :render-key wire
shape and the cloneElement/post-render attribution path remain emitted by the
retained adapters ([TRANSITION] in Spec 004); the compiled substrate emits
its own versioned schema from day one.
Resolved Decisions¶
- Attribution at the cause site — ruled. Evidence is emitted where the cause happens, never reconstructed from React internals.
:propcause precision — ruled. Changed top-level slots only; nested-path deep diff is an on-demand dev view, never a default emit.- Connection recorded as observed (name unified 2026-07-12). Three runtime states; hide/unmount are qualified retroactive annotations with named proofs; GC inference explicitly approximate.
- P1-5 docs/slot projection (2026-07-16, EP-0035). The dev/test projection is a versioned public shape with per-prop docs/defaults and slot metadata; render-slot sites join the site roster.
- One-catalogue scope qualified (2026-07-19). The one-catalogue rule
covers runtime-tier ids only, matching Spec 004 §One catalogue (runtime
tier); compile-tier
:rf.ui.compile/*ids live in the maintained compiler roster and deliberately get no Spec 009 rows. - Sub query vectors egress raw (2026-07-19, delegated). Query vectors are
identity, not payload, and do not route through the EP-0025 egress
chokepoint; the raw egress on query-vector-bearing slots is an accepted,
documented fail-open, backstopped only by the Spec 010 schema axis for
:sensitive?-schema'd subs.
Open Issues¶
- Shipped (rf2-vxgfnd.98.1, 2026-07-21). The evidence-schema delta
(§Shipped surface and specified extensions) shipped at S6 — the per-commit
committed-instance record, the six-kind
:rf.view/causesvector, the DOM annotation, and the Spec 009 rows — underre-frame.ui.tool/schema-version3; see §S6 view-evidence delta for the shipped shape and the deferred-with-triggers rows.:parent-render-key, the singular:frame-id, and five further cause kinds are deferred with named triggers, not dropped. - Deferred with a named trigger: a Static-mode mounted-views/manifest-browse
surface goes to the post-S3 IA review, triggered only if dogfooding shows
the Views-tab +
mounted-viewsassembly failing.
Graduation: the S6 evidence schema now asserts (rf2-vxgfnd.98.1) and the G-7/G-11
erasure gates hold; this EP moves to final once the deferred-with-triggers rows
recorded in §S6 view-evidence delta are either triggered-and-shipped or confirmed
errata.
References¶
- Primary normative home:
spec/004D-Freehand-Compiled-Grammar.md§View identity and the instrumentation surface;spec/009-Instrumentation.md(the runtime-tier catalogue);spec/011-SSR.md(root-id lives with its root manifests);spec/conformance/S3-view-conformance-profile.md(the certified S3 surface);tools/xray/spec/021(consumer tolerance of the unshipped delta). - EP-0030 — the umbrella: stages, demand bar, and the blessed API surface this evidence tier instruments.
- EP-0031 — the compiler,
defview, templates, and handler law whose sites the manifest indexes. - EP-0032 — observation targets and the committed push-ownership protocol; connected commit is the publication boundary for instance records, and the three-state connection lifecycle recorded here is that EP's observed-lifecycle contract.
- EP-0034 — owns the SSR/roots
story; the
:hydration-correctioncause names its server/client/probe disagreements; its G-7/G-11 gates prove this tier's production erasure. - EP-0035 — the readiness amendment package (render-slot manifest sites, the public docs/slot projection).
- EP-0008 — after erasure, production keeps exactly the always-on error channel; this evidence is the dev-only tier. EP-0025 — the egress-projection policy the record slots route through.
- Design provenance: the debugging study in the tombstoned synthesis tree
(
ai/findings/new-substrate-synthesis/); the S3 Xray consumption IA lands with W7a undertools/xray/spec/.