All files / src/routes/dashboard prepState.ts

71.42% Statements 5/7
80% Branches 4/5
100% Functions 1/1
71.42% Lines 5/7

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63                                              1x                                                 8x   2x   2x     4x              
/**
 * Single source of truth for the dashboard placeholder-phase prep-state
 * discriminants (issue #275).
 *
 * Mirrors `spec/programming.allium:27`
 *   enum PrepState { in_flight | replanning | stalled | unknown }
 * and the SECURITY DEFINER `get_dashboard_prep_state` SQL function
 * (`supabase/migrations/20260516153245_add_get_dashboard_prep_state.sql`), whose
 * priority order is stalled > replanning > in_flight > unknown (first match wins).
 *
 * Why a runtime tuple, not just a type union: the dashboard render branch
 * (`+page.svelte`), the load-function validation (`+page.server.ts`), and the
 * Playwright drift guard (`e2e/dashboard-prep-states.test.ts`) all derive from
 * this one list. A `type`-only union is erased at runtime and cannot be
 * iterated, so the drift guard could not assert "every discriminant has a
 * seeded render assertion". The `as const` tuple gives both a compile-time
 * exhaustiveness anchor (see `prepView`) and a runtime-iterable set.
 *
 * Conformance with the spec enum and the SQL function is by-eye — there is no
 * codegen from the Allium spec. If the spec enum gains a member, add it here:
 * `prepView`'s exhaustiveness guard then forces a render-branch decision at
 * compile time, and the #275 drift guard forces a seeded E2E assertion.
 */
export const PREP_STATES = ['in_flight', 'replanning', 'stalled', 'unknown'] as const;
 
export type PrepState = (typeof PREP_STATES)[number];
 
/**
 * Render category for the placeholder phase. Collapses the four discriminants
 * to the three branches the dashboard actually renders:
 *  - `stalled`    — terminal; oncall owns recovery (no Check-now, polling stops).
 *  - `replanning` — transient refusal, in-band retry in flight (keep Check-now).
 *  - `optimistic` — in_flight | unknown; the generating / preparing copy. The
 *                   split between the pre-60s "generating" notice and the
 *                   post-60s "preparing" fallback is time-based (the durable
 *                   >60s threshold), NOT discriminant-based, so both share this
 *                   category.
 */
export type PrepView = 'stalled' | 'replanning' | 'optimistic';
 
/**
 * Map a `PrepState` discriminant to the render category the dashboard branches
 * on. The `default` arm's `never` assignment is the exhaustiveness guard:
 * adding a discriminant to `PREP_STATES` without a case here is a compile error
 * (`pnpm check` / `tsc`), so a new state can never silently collapse to the
 * optimistic copy.
 */
export function prepView(state: PrepState): PrepView {
	switch (state) {
		case 'stalled':
			return 'stalled';
		case 'replanning':
			return 'replanning';
		case 'in_flight':
		case 'unknown':
			return 'optimistic';
		default: {
			const _exhaustive: never = state;
			return _exhaustive;
		}
	}
}