Context
Filed as the J3 leaf of #14781 under that epic's own rule — "first claimant per journey FILES the leaf — filing is reading." J2 is filed and done (#14840); J1 and J3 were both named unfiled in the epic body. This files the J3 slice that is definitively missing and release-gate blocking; it does not claim the epic steward seat, which #14781 still asks for separately.
#14781 defines the journey:
J3 — the stranger journey: fresh clone → self-configure → first persistence → first created widget, with TTFP measured by the harness (crosses: onboarding → config SSOT → keeper; the "minutes, measured" bar).
And #14790 Phase 2 makes it a gate rather than a nice-to-have:
the "download and run" CTA goes live ONLY when J3 … passes on a machine none of us prepared. The CTA's landing page shows the TTFP number with its provenance — measurement as marketing (nobody else publishes theirs).
The Problem
No instrument for first persistence exists anywhere. Verified at current dev — grep across harness/, apps/ and ai/ for firstPersist / persistenceMs / firstWriteMs / timeToFirstPersist returns nothing, and the string TTFP appears in no source file at all.
What the harness does measure is first paint: harness/main.mjs holds firstPaintReports / firstPaintWaiters and computes firstPaintMs, fed by preload.cjs's shell-first-paint-report once the cockpit renders recognised adapter heads.
Those are different subjects. First paint says the surface rendered; first persistence says the stranger's data became durable. A stranger can reach a painted cockpit with nothing saved, so publishing a paint number under a persistence claim would misdescribe the product on its own landing page — the one place the number is load-bearing.
This is not a naming quibble: #14790 commits to publishing TTFP with its provenance. A number whose provenance is "we measured a different event" cannot be published.
The Architectural Reality
The paint instrument is a good template and should be reused rather than replaced. Its shape is already correct for this: the preload observes, ipcMain receives on a private sender-validated channel, and the shell owns the verdict — preload.cjs's comment records why (webContents.executeJavaScript wedges on this SharedWorker-heavy page, so preload + IPC is the reliable observation channel).
Both clocks should coexist. First paint remains a real metric (renderer-load-to-semantic-ready); first persistence is the product metric. Neither replaces the other, and the receipt should carry both so the difference stays visible instead of being resolved by whoever reads it later.
The open question this ticket must settle FIRST — not an implementation detail
Which event constitutes "first persistence"? The answer determines what the published number means, so it needs a decision before code. Candidates, with what each would measure:
| Candidate event |
What the number would then mean |
| The stranger's config/credentials become durable |
"how long until setup sticks" — earliest, most flattering, and arguably the honest end of self-configure |
| First durable write through the keeper/registry |
"how long until the product retained something the user made" — matches J3's config SSOT → keeper crossing |
| First created widget persisted across relaunch |
strictest; but J3 lists "first created widget" as a separate step after first persistence, so this likely over-reaches |
J3's own sequence — self-configure → first persistence → first created widget — puts first persistence between configuration and widget creation, which argues for the config-durability reading. That is inference from ordering, not authority, so it is recorded here as a proposal rather than a decision.
Do not let the implementation pick this by proximity. Choosing whichever event is easiest to observe and calling it persistence is how an instrument ends up answering about the wrong subject — the exact failure that produced the Drop+Supersede on PR #16050 (a producer selected by name, whose actual return shape was a different fact).
Acceptance Criteria
Out of Scope
- The rest of J3 (fresh clone, self-configure UX, first-widget creation, the unprepared-machine run). This leaf delivers the instrument; the journey it measures is the epic's.
firstPaintMs semantics — unchanged.
- #14230 (@neo-gpt,
fork → install → try a lane → PR) is adjacent onboarding work and carries no TTFP instrument; no overlap.
Related
- Journey authority: #14781 (J3) · release gate: #14790 Phase 2 · shell surface: #14793 (AC1 names "measured first persistence")
- Subject-verification discipline: the PR #16050 Drop+Supersede review anchor
Context
Filed as the J3 leaf of #14781 under that epic's own rule — "first claimant per journey FILES the leaf — filing is reading." J2 is filed and done (#14840); J1 and J3 were both named unfiled in the epic body. This files the J3 slice that is definitively missing and release-gate blocking; it does not claim the epic steward seat, which #14781 still asks for separately.
#14781 defines the journey:
And #14790 Phase 2 makes it a gate rather than a nice-to-have:
The Problem
No instrument for first persistence exists anywhere. Verified at current
dev—grepacrossharness/,apps/andai/forfirstPersist/persistenceMs/firstWriteMs/timeToFirstPersistreturns nothing, and the stringTTFPappears in no source file at all.What the harness does measure is first paint:
harness/main.mjsholdsfirstPaintReports/firstPaintWaitersand computesfirstPaintMs, fed bypreload.cjs'sshell-first-paint-reportonce the cockpit renders recognised adapter heads.Those are different subjects. First paint says the surface rendered; first persistence says the stranger's data became durable. A stranger can reach a painted cockpit with nothing saved, so publishing a paint number under a persistence claim would misdescribe the product on its own landing page — the one place the number is load-bearing.
This is not a naming quibble: #14790 commits to publishing TTFP with its provenance. A number whose provenance is "we measured a different event" cannot be published.
The Architectural Reality
The paint instrument is a good template and should be reused rather than replaced. Its shape is already correct for this: the preload observes,
ipcMainreceives on a private sender-validated channel, and the shell owns the verdict —preload.cjs's comment records why (webContents.executeJavaScriptwedges on this SharedWorker-heavy page, so preload + IPC is the reliable observation channel).Both clocks should coexist. First paint remains a real metric (renderer-load-to-semantic-ready); first persistence is the product metric. Neither replaces the other, and the receipt should carry both so the difference stays visible instead of being resolved by whoever reads it later.
The open question this ticket must settle FIRST — not an implementation detail
Which event constitutes "first persistence"? The answer determines what the published number means, so it needs a decision before code. Candidates, with what each would measure:
config SSOT → keepercrossingJ3's own sequence —
self-configure → first persistence → first created widget— puts first persistence between configuration and widget creation, which argues for the config-durability reading. That is inference from ordering, not authority, so it is recorded here as a proposal rather than a decision.Do not let the implementation pick this by proximity. Choosing whichever event is easiest to observe and calling it persistence is how an instrument ends up answering about the wrong subject — the exact failure that produced the Drop+Supersede on PR #16050 (a producer selected by name, whose actual return shape was a different fact).
Acceptance Criteria
firstPaintMsrather than replacing it; the receipt carries both plus which event each measured.0, no "assume it happened". An unmeasured journey is unmeasured, not fast.appLifecycleinjection pattern, which deliberately runs without harness-local Electron).Out of Scope
firstPaintMssemantics — unchanged.fork → install → try a lane → PR) is adjacent onboarding work and carries no TTFP instrument; no overlap.Related