Files
archy/neode-ui/e2e/perf/keepalive-remount-probe.spec.ts
T
archipelagoandClaude Fable 5 4d471759a7
Demo images / Build & push demo images (push) Successful in 3m36s
fix(02-09): name Server/Web5 KeepAlive "remount" as a proven probe artifact
Task 1: built keepalive-remount-probe.spec.ts, a committed, re-runnable
Playwright spec covering every KEEP_ALIVE_PATHS tab with three added
instruments (instance-uid, session-wide console/pageerror capture, DOM
population/pathname logging per hop) beyond 02-08's ad-hoc probe.

Eliminated suspects 2 (runtime error), 3 (LRU eviction), 4 (route.path
mismatch) and 5 (include-name matching) by direct measurement. Confirmed
suspect 1 with positive proof: an authoritative document.elementFromPoint()
hit-test signal contradicted the naive selector-match method's "remounted"
verdict for both Server and Web5 across independent runs, and a companion
diagnostic found the original stamped root still connected+visible under
a different (unpicked) match. Root cause: Server, Web5 and Fleet share the
fully generic .view-container [data-controller-container] selector every
KeepAlive-cached main tab's root carries via fallthrough, which cannot
disambiguate "the foreground tab" from "another cached tab still connected
to the document" once more than one tab has been visited — the normal,
intended KeepAlive state. Settings (the away tab every round trip uses)
independently renders matching content too (AccountInfoSection/
KioskDisplaySection), compounding the ambiguity.

Server.vue and Web5.vue's instances survive tab round-trips exactly like
every other registered tab — no defect in DashboardRouterView.vue,
dashboardViewWrappers.ts, keepAliveRoutes.ts or Server.vue's KeepAlive
wiring. 02-FINDINGS.md records the full method, eliminated suspects and
verdict per D-10 (this commit lands before any src change, per gate).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 05:42:54 -04:00

531 lines
24 KiB
TypeScript

// keepalive-remount-probe.spec.ts — 02-09 gap-closure Task 1, Step B.
//
// Standalone, re-runnable Playwright spec that logs into the deployed
// archi-dev-box build (D-11) and, for EVERY path in KEEP_ALIVE_PATHS,
// performs a visit -> away (to the neutral /dashboard/settings tab) ->
// return round trip, reporting whether the component instance survived.
//
// Deliberately does NOT edit measure.ts / surfaces.ts / surface-perf.spec.ts
// — the 02-01 harness stays frozen so 02-10 can re-run it unmodified. This
// spec reuses SURFACES' navSteps/contentSelector/rootSelector (read-only
// import) as the click recipe to reach each tab, but implements its own
// corrected stamp/read method plus three instruments the ad-hoc 02-08 probe
// did not have:
//
// 1. A monotonic per-mount signal written by the page itself, independent
// of the DOM-element dataset stamp: Vue unconditionally attaches
// `el.__vueParentComponent` to every mounted root element (confirmed
// by reading node_modules/@vue/runtime-core's `mountElement`, not
// gated behind a dev-only flag), whose `.uid` is a per-instance
// monotonic counter. Reading this before/after the round trip gives an
// INSTANCE-identity signal that can disagree with the dataset-mark's
// ELEMENT-identity signal if the probe's selector picks a different
// cached instance than the one actually under test (suspect 1).
// 2. `page.on('pageerror')` / `page.on('console')` captured for the whole
// round trip and printed — a Server-specific runtime error during
// activation/deactivation (suspect 2) would surface here even though
// it might not throw synchronously enough to fail the Playwright step
// itself.
// 3. After each hop: `document.querySelectorAll('.view-container').length`
// and `location.pathname`, so cache population and the actual route
// path are visible in the transcript (suspects 3 and 4).
//
// Corrected remount method (documented in 02-FINDINGS.md's ## Results
// preamble): stamp/read the `.view-container`-class ANCESTOR (or the
// surface's own `rootSelector`, for the two surfaces — Mesh, Chat — that
// don't use `.view-container`) of the surface's own VISIBLE contentSelector
// match, using `getBoundingClientRect`/`offsetParent` to exclude KeepAlive's
// inactive cached instances — never `document.querySelector`'s first DOM
// match, which can silently pick a different cached instance once multiple
// `.view-container`s coexist in the document at once.
import { expect, test, type Page } from '@playwright/test'
import { SURFACES, type Surface } from './surfaces'
// Re-declared rather than imported from '@/views/dashboard/keepAliveRoutes':
// the e2e package's tsconfig (see tsconfig.app.json's `include`) does not
// cover `e2e/**`, so the `@/*` path alias used across neode-ui/src is not
// guaranteed to resolve under Playwright's own TS transform. surfaces.ts and
// measure.ts already avoid the alias for the same reason (no `@/` import in
// either file) — this list is the exact literal from keepAliveRoutes.ts
// (TAB_ORDER minus withheld `/dashboard/settings`, plus `/dashboard/discover`).
const KEEP_ALIVE_PATHS = new Set<string>([
'/dashboard',
'/dashboard/apps',
'/dashboard/marketplace',
'/dashboard/cloud',
'/dashboard/mesh',
'/dashboard/server',
'/dashboard/web5',
'/dashboard/fleet',
'/dashboard/chat',
'/dashboard/discover',
])
const PASSWORD = process.env.ARCHY_PASSWORD ?? 'password123'
const NEUTRAL_SELECTOR = '[data-controller-zone="sidebar"] a[href="/dashboard/settings"]'
const NAV_TIMEOUT = 20_000
const CONTENT_TIMEOUT = 20_000
async function login(page: Page): Promise<void> {
// Mirrors surface-perf.spec.ts's login() (itself mirroring app-launch.spec.ts)
// verbatim — do not invent a second auth path.
await page.goto('/login', { waitUntil: 'domcontentloaded' })
await page.evaluate(() => {
localStorage.setItem('neode_intro_seen', '1')
localStorage.setItem('neode_onboarding_complete', '1')
})
await page.goto('/login', { waitUntil: 'networkidle' })
const passwordInput = page.locator('input[type="password"]').first()
await passwordInput.waitFor({ timeout: 15_000 })
await passwordInput.fill(PASSWORD)
await page
.locator('button:has-text("Login"), button:has-text("Unlock"), button:has-text("Continue"), button[type="submit"]')
.first()
.click()
await page.waitForURL('**/dashboard**', { timeout: 20_000 })
}
interface HopSnapshot {
viewContainerCount: number
pathname: string
}
async function snapshotHop(page: Page): Promise<HopSnapshot> {
return page.evaluate(() => ({
viewContainerCount: document.querySelectorAll('.view-container').length,
pathname: location.pathname,
}))
}
/**
* Wait until the DOM has stopped churning: the raw element count for
* `contentSelector` (NOT filtered by visibility — some contentSelectors,
* e.g. `.home-card`, legitimately match several sibling cards at once, so
* "exactly 1" is the wrong invariant) reads identically across 5 consecutive
* 100ms-spaced polls.
*
* Discovered mid-investigation (not hypothesized up front): `/dashboard/settings`
* — the away tab EVERY round trip in this probe (and in measure.ts's own
* `NEUTRAL_SELECTOR` convention) uses — renders `AccountInfoSection.vue` and
* `KioskDisplaySection.vue` unconditionally (Settings.vue has no tabs/
* accordion gating), and BOTH carry `data-controller-container`. Settings'
* own root ALSO gets the `view-container` fallthrough class (every non-full-
* bleed route does). That means Settings' OWN content matches the exact
* generic `.view-container [data-controller-container]` selector Server,
* Web5 and Fleet all use — and Settings' leave-transition keeps its DOM
* genuinely present (in the document, still counted by querySelectorAll)
* for the transition's full duration while the RETURN target's enter-
* transition is already progressing. A stamp/read taken during that overlap
* window can silently pick the still-present-but-leaving Settings element
* instead of the actual target — a false "remounted" verdict that has
* nothing to do with KeepAlive at all. Waiting for the raw count to settle
* lets the leave-transition finish (Vue's <Transition> removes the leaving
* element from the DOM once its leave hook completes) before this probe
* stamps or reads anything.
*/
async function waitForDomSettled(page: Page, contentSelector: string, timeoutMs: number): Promise<void> {
await page.evaluate((sel) => {
const w = window as unknown as { __probeStableCount?: number; __probeLastLen?: number }
w.__probeStableCount = 0
w.__probeLastLen = document.querySelectorAll(sel).length
}, contentSelector)
await page.waitForFunction(
(sel) => {
const w = window as unknown as { __probeStableCount?: number; __probeLastLen?: number }
const len = document.querySelectorAll(sel).length
if (w.__probeLastLen === len) {
w.__probeStableCount = (w.__probeStableCount ?? 0) + 1
} else {
w.__probeStableCount = 0
}
w.__probeLastLen = len
return (w.__probeStableCount ?? 0) >= 5
},
contentSelector,
{ timeout: timeoutMs, polling: 100 }
)
}
interface ProbeReading {
ok: boolean
uid: number | null
typeName: string | null
}
/** Locate the VISIBLE contentSelector match's rootSelector ancestor (or
* self, if contentSelector === rootSelector) and stamp it with `mark`,
* recording the Vue instance uid/type-name found on it at the same moment. */
async function stampVisibleRoot(page: Page, contentSelector: string, rootSelector: string, mark: string): Promise<ProbeReading> {
return page.evaluate(
({ contentSelector, rootSelector, mark }) => {
const candidates = Array.from(document.querySelectorAll(contentSelector)) as HTMLElement[]
const visible = candidates.find((el) => {
const rect = el.getBoundingClientRect()
return el.offsetParent !== null && (rect.width > 0 || rect.height > 0)
})
if (!visible) return { ok: false, uid: null, typeName: null }
const root = (visible.closest(rootSelector) as HTMLElement | null) ?? (visible.matches(rootSelector) ? visible : null)
if (!root) return { ok: false, uid: null, typeName: null }
root.dataset.perfProbeMark = mark
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const comp = (root as any).__vueParentComponent ?? null
const uid: number | null = comp ? (comp.uid ?? null) : null
const typeName: string | null = comp ? (comp.type?.__name ?? comp.type?.name ?? null) : null
return { ok: true, uid, typeName }
},
{ contentSelector, rootSelector, mark }
)
}
interface RootMarkDebug {
mark: string | null
visible: boolean
connected: boolean
}
interface ReadResult {
ok: boolean
markMatches: boolean
uid: number | null
typeName: string | null
allRootMarks: RootMarkDebug[]
/** The authoritative "what does the user actually see" signal:
* `document.elementFromPoint()` at the viewport center performs real hit
* testing (respects stacking/z-index/opacity), unlike the
* `offsetParent`/`getBoundingClientRect` heuristic used above, which
* cannot distinguish the true foreground root from another root that
* merely has non-zero layout dimensions while stacked behind it. */
elementFromPointMark: string | null
}
async function readVisibleRoot(
page: Page,
contentSelector: string,
rootSelector: string,
expectedMark: string
): Promise<ReadResult> {
return page.evaluate(
({ contentSelector, rootSelector, expectedMark }) => {
// Diagnostic: every element matching rootSelector ANYWHERE in the
// document (not just the one reachable from the visible content
// match), reporting its own mark + visibility. If the ORIGINAL
// stamped root still carries its mark but is not the one this read
// considers "visible", that is a completely different finding
// (a still-alive-but-orphaned cached instance) than the mark being
// gone from every root entirely (a genuine destroy+recreate).
const allRoots = Array.from(document.querySelectorAll(rootSelector)) as HTMLElement[]
const allRootMarks = allRoots.map((el) => {
const rect = el.getBoundingClientRect()
return {
mark: el.dataset.perfProbeMark ?? null,
visible: el.offsetParent !== null && (rect.width > 0 || rect.height > 0),
connected: el.isConnected,
}
})
// Authoritative real-hit-test signal, independent of the
// offsetParent/rect heuristic above.
const cx = Math.floor(window.innerWidth / 2)
const cy = Math.floor(window.innerHeight / 2)
const topEl = document.elementFromPoint(cx, cy) as HTMLElement | null
const topRoot = (topEl?.closest(rootSelector) as HTMLElement | null) ?? null
const elementFromPointMark = topRoot?.dataset.perfProbeMark ?? null
const candidates = Array.from(document.querySelectorAll(contentSelector)) as HTMLElement[]
const visible = candidates.find((el) => {
const rect = el.getBoundingClientRect()
return el.offsetParent !== null && (rect.width > 0 || rect.height > 0)
})
if (!visible) return { ok: false, markMatches: false, uid: null, typeName: null, allRootMarks, elementFromPointMark }
const root = (visible.closest(rootSelector) as HTMLElement | null) ?? (visible.matches(rootSelector) ? visible : null)
if (!root) return { ok: false, markMatches: false, uid: null, typeName: null, allRootMarks, elementFromPointMark }
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const comp = (root as any).__vueParentComponent ?? null
const uid: number | null = comp ? (comp.uid ?? null) : null
const typeName: string | null = comp ? (comp.type?.__name ?? comp.type?.name ?? null) : null
return { ok: true, markMatches: root.dataset.perfProbeMark === expectedMark, uid, typeName, allRootMarks, elementFromPointMark }
},
{ contentSelector, rootSelector, expectedMark }
)
}
interface RoundTripResult {
path: string
label: string
/** Primary verdict: did the SAME element survive the round trip (the
* corrected 02-08 method's own signal)? null = could not be probed. */
elementSurvived: boolean | null
/** Independent instance-identity signal (instrument 1): did the SAME Vue
* component instance (by internal uid) survive? null = could not be read
* (e.g. __vueParentComponent absent, or the view was never found). */
instanceSurvived: boolean | null
instanceTypeNameBeforeAway: string | null
instanceTypeNameAfterReturn: string | null
/** Diagnostic: every rootSelector-matching element in the document at
* read-back time, with its own mark + visibility/connected state — shows
* whether an unmatched original root is genuinely gone vs still present
* (just not the one the visibility filter picked). */
allRootMarksAtRead: RootMarkDebug[]
/** Authoritative real-hit-test signal at read-back time (see ReadResult's
* own doc comment) — null means either the read failed or elementFromPoint
* found no rootSelector ancestor at the viewport center. */
elementFromPointMarkAtRead: string | null
elementFromPointSurvived: boolean | null
afterVisit: HopSnapshot | null
afterAway: HopSnapshot | null
afterReturn: HopSnapshot | null
consoleMessages: string[]
pageErrors: string[]
error: string | null
}
/**
* Best-effort dismissal of a stray full-screen overlay before it blocks the
* next click — mirrors measure.ts's own `dismissOverlays()` (this spec
* deliberately does not import from measure.ts, so the logic is duplicated
* here rather than shared, per the "don't edit the frozen 02-01 harness"
* constraint). archi-dev-box currently runs at 85% disk (02-FINDINGS.md
* Outstanding), which keeps `HealthNotifications.vue`'s disk-usage toast
* live for the whole session; that toast's `.fixed.inset-0…z-[3000]` wrapper
* has no `pointer-events: none`, so it silently intercepts clicks on
* whatever sits behind it — an environmental condition unrelated to the
* KeepAlive remount question this probe exists to answer, and one this
* probe must route around rather than be blocked by.
*/
async function dismissOverlays(page: Page): Promise<void> {
try {
await page.keyboard.press('Escape')
} catch {
// no-op — best-effort only
}
const closeButtons = page.locator(
'[role="dialog"] button[aria-label*="Close" i], .fixed.inset-0 button[aria-label*="Close" i]'
)
const count = await closeButtons.count().catch(() => 0)
if (count > 0) {
await closeButtons.first().click({ timeout: 2_000 }).catch(() => {})
}
}
async function clickWithGuard(page: Page, selector: string, timeoutMs: number): Promise<void> {
const attempts = 3
const perAttemptMs = Math.max(2_000, Math.floor(timeoutMs / attempts))
let lastErr: unknown
for (let i = 0; i < attempts; i++) {
await dismissOverlays(page)
try {
await page.locator(selector).first().click({ timeout: perAttemptMs })
return
} catch (err) {
lastErr = err
}
}
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr))
}
async function goHome(page: Page): Promise<void> {
if (new URL(page.url()).pathname === '/dashboard/chat') {
await page.locator('.chat-close-btn').first().click({ timeout: 5_000 }).catch(() => {})
}
await clickWithGuard(page, '[data-controller-zone="sidebar"] a[href="/dashboard"]', NAV_TIMEOUT)
await page.waitForURL((url) => url.pathname === '/dashboard', { timeout: NAV_TIMEOUT })
}
async function clickChain(page: Page, steps: string[]): Promise<void> {
for (const selector of steps) {
await clickWithGuard(page, selector, NAV_TIMEOUT)
}
}
async function roundTrip(page: Page, surface: Surface): Promise<RoundTripResult> {
const consoleMessages: string[] = []
const pageErrors: string[] = []
const onConsole = (msg: { type: () => string; text: () => string }) => {
consoleMessages.push(`[${msg.type()}] ${msg.text()}`)
}
const onPageError = (err: Error) => {
pageErrors.push(err.message)
}
page.on('console', onConsole)
page.on('pageerror', onPageError)
let afterVisit: HopSnapshot | null = null
let afterAway: HopSnapshot | null = null
let afterReturn: HopSnapshot | null = null
let stampReading: ProbeReading = { ok: false, uid: null, typeName: null }
let readReading: ReadResult = { ok: false, markMatches: false, uid: null, typeName: null, allRootMarks: [], elementFromPointMark: null }
try {
await goHome(page)
await clickChain(page, surface.navSteps)
// Wait for the URL first, THEN the content selector: three of these ten
// surfaces (Server, Web5, Fleet) share the generic
// `.view-container [data-controller-container]` contentSelector
// (02-FINDINGS.md's own documented ambiguity), so waiting on the
// selector alone can resolve instantly against the PREVIOUS tab's still-
// visible content before the navigation actually lands — a probe
// artifact this instrumentation (pathname logging, instrument 3) caught
// directly rather than one I could have caught by inspection alone.
if (!surface.path.includes(':')) {
await page.waitForURL((url) => url.pathname === surface.path, { timeout: NAV_TIMEOUT })
}
await page.locator(surface.contentSelector).first().waitFor({ state: 'visible', timeout: CONTENT_TIMEOUT })
await waitForDomSettled(page, surface.contentSelector, CONTENT_TIMEOUT)
afterVisit = await snapshotHop(page)
const mark = `probe-${Date.now()}-${Math.random().toString(36).slice(2)}`
stampReading = await stampVisibleRoot(page, surface.contentSelector, surface.rootSelector, mark)
if (surface.closeSelector) {
// Deliberately NOT routed through clickWithGuard: dismissOverlays()
// treats any "Close"-labelled dialog button as a stray overlay to
// dismiss, which is exactly this element for a modal-trigger surface
// — same reasoning as measure.ts's own runOnce().
await page.locator(surface.closeSelector).first().click({ timeout: NAV_TIMEOUT })
} else {
await clickWithGuard(page, NEUTRAL_SELECTOR, NAV_TIMEOUT)
await page.waitForURL((url) => url.pathname === '/dashboard/settings', { timeout: NAV_TIMEOUT })
}
afterAway = await snapshotHop(page)
await clickChain(page, surface.navSteps)
if (!surface.path.includes(':')) {
await page.waitForURL((url) => url.pathname === surface.path, { timeout: NAV_TIMEOUT })
}
await page.locator(surface.contentSelector).first().waitFor({ state: 'visible', timeout: CONTENT_TIMEOUT })
await waitForDomSettled(page, surface.contentSelector, CONTENT_TIMEOUT)
afterReturn = await snapshotHop(page)
readReading = await readVisibleRoot(page, surface.contentSelector, surface.rootSelector, mark)
const elementSurvived = stampReading.ok && readReading.ok ? readReading.markMatches : null
const instanceSurvived =
stampReading.ok && readReading.ok && stampReading.uid != null && readReading.uid != null
? stampReading.uid === readReading.uid
: null
const elementFromPointSurvived = readReading.elementFromPointMark != null ? readReading.elementFromPointMark === mark : null
return {
path: surface.path,
label: surface.label,
elementSurvived,
instanceSurvived,
instanceTypeNameBeforeAway: stampReading.typeName,
instanceTypeNameAfterReturn: readReading.typeName,
allRootMarksAtRead: readReading.allRootMarks,
elementFromPointMarkAtRead: readReading.elementFromPointMark,
elementFromPointSurvived,
afterVisit,
afterAway,
afterReturn,
consoleMessages,
pageErrors,
error: null,
}
} catch (err) {
return {
path: surface.path,
label: surface.label,
elementSurvived: null,
instanceSurvived: null,
instanceTypeNameBeforeAway: stampReading.typeName,
instanceTypeNameAfterReturn: readReading.typeName,
allRootMarksAtRead: readReading.allRootMarks,
elementFromPointMarkAtRead: readReading.elementFromPointMark,
elementFromPointSurvived: null,
afterVisit,
afterAway,
afterReturn,
consoleMessages,
pageErrors,
error: err instanceof Error ? err.message : String(err),
}
} finally {
page.off('console', onConsole)
page.off('pageerror', onPageError)
}
}
test('keepalive-remount-probe: every KEEP_ALIVE_PATHS surface survives a tab round-trip (or reports why it could not be probed)', async ({ page }) => {
test.setTimeout(10 * 60 * 1000)
await login(page)
// Session-wide capture, in addition to roundTrip()'s own per-surface
// listeners: a delayed/async error (e.g. a promise that settles after a
// round trip's own listeners are already detached, mid-flight when we've
// moved on to the next surface) would otherwise be silently missed and
// wrongly attributed to "no error" for the surface actually responsible.
const sessionLog: string[] = []
const sessionStart = Date.now()
const onSessionConsole = (msg: { type: () => string; text: () => string }) => {
sessionLog.push(`[+${Date.now() - sessionStart}ms] [console:${msg.type()}] ${msg.text()}`)
}
const onSessionPageError = (err: Error) => {
sessionLog.push(`[+${Date.now() - sessionStart}ms] [pageerror] ${err.message}`)
}
page.on('console', onSessionConsole)
page.on('pageerror', onSessionPageError)
// De-duplicate by path, keeping the FIRST matching SURFACES row: two rows
// share `path: '/dashboard'` (the `home` main-tab row and the `wallet-send`
// modal-trigger row, which records Home's own path only for reference,
// per surfaces.ts's own doc comment on `closeSelector`) — the modal is not
// itself a KEEP_ALIVE_PATHS-registered route, so only `home` should count.
const seenPaths = new Set<string>()
const probeSurfaces = SURFACES.filter((s) => {
if (!KEEP_ALIVE_PATHS.has(s.path) || seenPaths.has(s.path)) return false
seenPaths.add(s.path)
return true
})
expect(probeSurfaces.length).toBe(KEEP_ALIVE_PATHS.size)
const results: RoundTripResult[] = []
for (const surface of probeSurfaces) {
const result = await roundTrip(page, surface)
results.push(result)
// eslint-disable-next-line no-console
console.log(
`[keepalive-remount-probe] ${result.path} (${result.label}): ` +
`elementSurvived=${result.elementSurvived} instanceSurvived=${result.instanceSurvived} ` +
`typeName(before/after)=${result.instanceTypeNameBeforeAway}/${result.instanceTypeNameAfterReturn} ` +
`viewContainerCount(visit/away/return)=${result.afterVisit?.viewContainerCount ?? 'n/a'}/${result.afterAway?.viewContainerCount ?? 'n/a'}/${result.afterReturn?.viewContainerCount ?? 'n/a'} ` +
`pathname(visit/return)=${result.afterVisit?.pathname ?? 'n/a'}/${result.afterReturn?.pathname ?? 'n/a'} ` +
`elementFromPointSurvived=${result.elementFromPointSurvived} ` +
`pageErrors=${result.pageErrors.length} error=${result.error ?? 'none'}`
)
if (result.pageErrors.length > 0) {
// eslint-disable-next-line no-console
console.log(`[keepalive-remount-probe] pageErrors: ${JSON.stringify(result.pageErrors)}`)
}
if (result.elementSurvived === false) {
// eslint-disable-next-line no-console
console.log(`[keepalive-remount-probe] allRootMarksAtRead (${result.path}): ${JSON.stringify(result.allRootMarksAtRead)}`)
}
}
page.off('console', onSessionConsole)
page.off('pageerror', onSessionPageError)
// eslint-disable-next-line no-console
console.log(`[keepalive-remount-probe] full session log (${sessionLog.length} entries):\n${sessionLog.join('\n')}`)
// eslint-disable-next-line no-console
console.log(`[keepalive-remount-probe] full results JSON:\n${JSON.stringify(results, null, 2)}`)
// Structural assertion only — every registered path must have been
// attempted and produce a result row, mirroring surface-perf.spec.ts's own
// sole assertion (`expect(results.length).toBe(SURFACES.length)`). Whether
// each one SURVIVED, or could even be probed at all, is the finding this
// probe exists to produce, not a pass/fail gate on the spec itself: Mesh's
// device-not-reporting-connected condition and Chat's AIUI-connection
// timing are both pre-existing, environment-dependent blockers
// 02-FINDINGS.md's own `## Results` section already documents as
// "unmeasured" rather than "failed" — an errored sample is recorded, never
// discarded and never used to fail the harness itself, exactly like
// `measure.ts`'s `measureSurface()` treats its own per-run errors.
expect(results.length).toBe(probeSurfaces.length)
})