Skip to main content

In-app diagnostics surface

A short, opinionated tour of how the in-app diagnostics screen in the gnostikon-client is put together. Aimed at contributors deciding whether to extend or change the surface — not at end users.

What this surface is

The diagnostics screen is an in-app view that lets a signed-in user inspect the state of their own product-events outbox, the server-side history of events accepted on their behalf, and a handful of runtime fields exposed by the client. It composes three sources at render time:

  1. Local outboxes — what the client has emitted but not yet shipped.
  2. Transient server-side history — what the backend confirms it has accepted, fetched live via the self-read endpoints documented in the API reference.
  3. Runtime state — client-side values that are useful to ops (request freshness, environment metadata, current user identity).

The screen is gated in production behind a discoverable-but-not-advertised gesture (see "7-tap-version unlock" below).

Non-persistent render path

The render path is non-persistent by design. Nothing the diagnostics screen displays is written to local storage; nothing survives a page reload beyond what each underlying source already persists for its own reasons (the outboxes use their normal persistence; the server-side history fetches fresh on mount).

The motivation is to avoid migrations on response-shape changes. The diagnostics screen is the place we accept that the rendered data may change shape with every backend release; making it durable would force a migration discipline that is not paid back by user value. By keeping the surface transient, schema drift on the read side is absorbed by the next mount, not by a schema-versioning protocol.

Cached-snapshot pattern for useSyncExternalStore

The diagnostics screen subscribes to several module-level stores via React's useSyncExternalStore. Each store follows the same convention (AGENTS.md rule 9 in the client repo): a single cachedSnapshot is rebuilt inside the store's listener notification, before notifying React subscribers. The React subscriber's getSnapshot always returns the latest cached value synchronously, so React's reconciliation has a stable reference.

let cachedSnapshot = buildSnapshot(state);
const listeners = new Set<() => void>();

function notify() {
cachedSnapshot = buildSnapshot(state); // rebuild before notify
for (const l of listeners) l();
}

export function subscribe(l: () => void) {
listeners.add(l);
return () => listeners.delete(l);
}
export function getSnapshot() {
return cachedSnapshot;
}

The alternative — rebuilding inside getSnapshot — is the classic "infinite loop" footgun under React 18+/19 strict mode. Putting the rebuild in the notification path guarantees referential stability for any number of synchronous calls.

7-tap-version unlock (prod entry-point)

In production builds, the /diagnostics route is not linked from any ordinary navigation surface. The user reaches it by tapping the version label on the Account screen seven times within 1500 ms of the first tap.

The gate is:

  • In-memory only. No AsyncStorage, no SecureStore, no persisted unlock flag. Killing and relaunching the app resets the counter.
  • Rolling 1500 ms window. Each tap starts a timer; the seventh tap must arrive before the window expires or the counter resets.
  • Not a security mechanism. It is an ops-facing easter-egg that keeps the surface out of the way for normal users. Treat it as discoverability control, not authorization. Authorization is enforced by the self-read contract on the backend.

In dev and staging builds, the route is reachable directly without the gesture.

Resource policy enumeration

The client's request-freshness store exposes a single accessor that returns the last-success timestamp of every cached resource, keyed by its resourceKey:

const freshness = requestFreshnessStore.getAll();
// → { [resourceKey: string]: { lastSuccessAt: number | null } }

The diagnostics screen iterates over this map to render "last fetched" timestamps per policy. Adding a new cached resource elsewhere in the client automatically surfaces it on the diagnostics screen — no per-resource plumbing required. The screen is a faithful reflection of whatever the store currently tracks.

Source of truth for the API contract

The diagnostics surface consumes endpoints from the canonical Gnostikon backend OpenAPI document. This site serves a copy at /openapi.json, refreshed via the local sync script:

# Default: read from sibling repo ../gnostikon/api/openapi.json
npm run sync:openapi

# Fallback: fetch from a running deployment
OPENAPI_SOURCE_URL=https://<host>/api/openapi.json npm run sync:openapi

The script writes static/openapi.json verbatim from whichever source ran; priority is OPENAPI_SOURCE_URL env var → sibling repo → fail with a hint. The script docstring is the long-form reference. Never hand-edit static/openapi.json — the sync is the only sanctioned write path (Constitution Principle I).

  • Diagnostics self-read invariants — the privacy contracts of the endpoints this screen consumes.
  • API reference — request/response shapes of the self-read endpoints.
  • gnostikon-client — source for the screen, the stores, and the useSyncExternalStore convention referenced above.
  • Client shell vocabulary — canonical names for the chrome surfaces this architecture page references (Rail, TopBar, Inspector, Knowledge workspace, etc.).