import type { UAClass } from "@vincentt-xr/harness/tunnel"; /** The roster block prints once, on the FOURTH TESTER's arrival, and never again. */ export declare const ROSTER_AT = 4; /** * The reconnect advisory's window (Flow L17). It prints only when a connection * attaches within 120s of a disconnect WITH THE SAME UA CLASS. On a first * connection, or at a demo where four fresh phones arrive, the creator never * sees it. */ export declare const RECONNECT_WINDOW_MS = 120000; export interface Viewer { readonly label: string; readonly uaClass: UAClass; readonly connectedAt: number; /** The tester this connection resolved to, once the harness introduced itself. */ readonly tester?: string; } /** One printable line, or a block of them. The runner writes; this decides. */ export interface ViewerLines { /** `tester-1 connected · iPhone · Safari` */ readonly connect?: string; /** * The `!` reconnect advisory, printed BELOW the connect line so the connect * line stays scannable. Held rather than printed immediately — see * `attribute`. */ readonly advisory?: string[]; /** The one-time roster block, on the fourth tester. */ readonly roster?: string[]; } export declare class ViewerRoster { private readonly live; /** Connections seen, ever. Printed once at the end; never a count of people. */ private total; private rosterPrinted; /** The most recent disconnect, for the L17 adjacency check. */ private lastDeparture; /** Testers ever seen live, so a refresh is recognised as a return. */ private readonly testersSeen; /** * A held advisory per connection, keyed by label. The connection arrives before * the harness mounts, so at connect time we do not yet know whether this is a * known tester — and a hedge printed beside a definite `tester-1 refreshed` * teaches the creator to distrust the definite line. So the advisory is * DEFERRED here and dropped if attribution lands. A retracted line in a piped * transcript is worse than a late one. */ private readonly heldAdvisory; /** Live CONNECTIONS. The console counts these; the CLI does not display them. */ get size(): number; get totalConnections(): number; /** Live TESTERS — distinct, attributed, currently connected. The display count. */ get testerCount(): number; /** Live connections with no tester yet: the pre-mount window, or a crashed app. */ get unattributedCount(): number; labels(): string[]; has(label: string): boolean; get(label: string): Viewer | undefined; /** Distinct live testers, in arrival order. */ liveTesters(): string[]; /** * connect records a CONNECTION. It prints nothing about a tester yet, because * the tester is not known until the app mounts — see `attribute`, which is * where the line a creator reads comes from. * * Idempotent per label: the tunnel client fires onViewer on EVERY OPEN frame, * not once per connection, so a viewer that requests forty assets would * otherwise announce itself forty times. */ connect(label: string, uaClass: UAClass, now: number): ViewerLines; /** * The harness introduced itself: this connection belongs to `tester`. This is * where the creator's line comes from. * * Returns the connect line on a tester's FIRST connection, `refreshed` when a * tester already seen comes back, and nothing for a second concurrent * connection belonging to a tester already live — which is the ordinary case * for one tab, and the reason one scan now reads as one thing. */ attribute(label: string, tester: string): ViewerLines; /** * The advisory leads with the RULE and follows with the instance. Leading with * a guess about this specific connection would teach the creator a hedge rather * than a behavior, and one uncertain sentence on a screen where everything * else is fact teaches them to distrust the labels generally — which is the * opposite of what the labels are for. * * It names CONNECTIONS, not devices, and closes on the fact that testers * survive a refresh — so the remedy for the uncertainty is on the same line as * the uncertainty. */ private reconnectAdvisory; disconnect(label: string, now: number): ViewerLines; /** * The roster is a ONE-TIME SIGNPOST, not a live-updating footer. A redrawn * status bar fights Ctrl-C, corrupts scrollback, breaks when the output is * piped to a file — which is exactly what an agent does with it — and turns a * plain stream into a TUI, which is a different product. * * It LEADS WITH A COUNT, and the count is exact: distinct attributed testers * currently live. Never the number ever assigned, never a connection count. * The old disclaimer ("this is not a device count") is gone — it existed only * because the number above it was wrong. */ rosterBlock(): string[]; /** * The `--viewer` miss (Flow L16). An unknown label is a FACT, not an error, * because labels churn — and the remedy is on the same line. */ notFollowing(label: string): string; /** The `--tester` miss. Same shape, different coordinate. */ notFollowingTester(tester: string): string; }