Kill screen fixtures: live/empty screens + one demo snapshot for e2e

The app is now fixture-free. All product screens render live data (via the
reconcile bridge + view builders) or an EmptyState — no hardcoded demo data,
metrics, or prose anywhere in the UI.

- Removed the `?? FIXTURE` fallbacks and embedded fake strings (fixed dates,
  'reconcile 3.2s', 'ahead of forecast', mock forecast prose) from all 10 product
  screens; each now shows an EmptyState when its live data is absent.
- directives-screen: fully live off pmstate.directives() (was seeded from a
  fixture with a fake consequence-diff card) — loading + empty states, no seed.
- use-chat: drop the CHAT seed (the model-status effect already sets the greeting).
- data/fixtures.ts → data/view-types.ts: stripped every const design fixture,
  kept only the view-model TYPES (the builder↔screen contract). Repointed imports.

Testing fixture (no live gitea), per the user's call:
- New apps/desktop/src/main/demo-snapshot.ts — one deterministic, gitea-shaped
  snapshot (issues/milestones/deps/timelines + capacity + directives + collaborators).
  Served by the reconcile/boot/collaborators/capacity/directives handlers when
  COMMITEA_E2E=1, so e2e renders it through the REAL builders with no token.
- smoke.spec rewritten to assert on the demo snapshot through the live render path;
  onboarding/offline-toggle assertions dropped (wizard is live → live-onboarding;
  offline is real state now). 12/12 smoke pass.

Verify: core tsc clean · desktop tsc clean · 12/12 smoke green (perf#32 benchmark
flakes under machine load — unrelated, passes in isolation).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Croissant Le Doux
2026-07-10 13:21:47 -04:00
parent 27edbbd6af
commit a04714bfa6
24 changed files with 678 additions and 818 deletions

View File

@@ -0,0 +1,152 @@
/**
* Deterministic demo snapshot — the ONLY test fixture, served by the main process
* when COMMITEA_E2E=1 so the e2e suite (and a no-token first look) runs WITHOUT a
* live Gitea. It's gitea-shaped on purpose: the reconcile handlers return it and
* the app renders it through the exact same view builders (schedule, forecast,
* lifecycle, standup, runway) as real data. There are no screen-level mocks — this
* is the single source of demo truth. Not shipped behavior; test infrastructure.
*/
import {
type CapacityMember,
type DependencyEdge,
type DirectiveRecord,
extractLabelFacts,
type GiteaIssue,
type GiteaMilestone,
type LifecycleEvent,
} from '@commitea/core'
const HOST = 'https://gitea.demo.local'
const OWNER = 'teatime'
const REPO = 'commitea'
/** Fixed clock so lifecycle math + forecasts are stable across runs. */
export const DEMO_TODAY = '2026-03-02T09:00:00Z'
interface Seed {
number: number
title: string
labels: string[]
state?: 'open' | 'closed'
milestone?: number
assignee?: string
events?: LifecycleEvent[]
closedAt?: string
createdAt?: string
}
const MILESTONES_RAW = [
{ id: 1, title: 'Beta', description: 'Pilot-ready', dueOn: '2026-03-12T00:00:00Z', state: 'open' as const },
{ id: 2, title: 'v1.0', description: 'General availability', dueOn: '2026-05-01T00:00:00Z', state: 'open' as const },
]
const MREF = (id: number) => {
const m = MILESTONES_RAW.find((x) => x.id === id)!
return { id: m.id, title: m.title, dueOn: m.dueOn }
}
// A small, believable backlog for a team building CommiTea. Timelines place issues
// across the five lifecycle columns: bare → diagnosis, labeled → triage, a commit
// ref → steeping, a PR ref → review, closed → done.
const SEEDS: Seed[] = [
{ number: 42, title: 'Fix lifecycle inference on merge events', labels: ['est/3d', 'p/1', 'deadline/hard'], milestone: 1, assignee: 'stephen',
events: [{ type: 'triage', at: '2026-02-24T10:00:00Z' }, { type: 'commit', at: '2026-02-27T14:00:00Z' }] },
{ number: 41, title: 'Monte Carlo forecast bands on the runway', labels: ['est/5d', 'p/1'], milestone: 1, assignee: 'ana',
events: [{ type: 'triage', at: '2026-02-23T09:00:00Z' }, { type: 'commit', at: '2026-02-26T09:00:00Z' }, { type: 'pull', at: '2026-03-01T16:00:00Z' }] },
{ number: 40, title: 'Encrypted token store in the main process', labels: ['est/2d', 'p/2'], milestone: 1, assignee: 'stephen',
events: [{ type: 'triage', at: '2026-02-22T11:00:00Z' }] },
{ number: 39, title: 'Deterministic scheduler with capacity lanes', labels: ['est/8d', 'p/2'], milestone: 1, assignee: 'ana',
events: [{ type: 'triage', at: '2026-02-20T09:00:00Z' }, { type: 'commit', at: '2026-02-28T09:00:00Z' }] },
{ number: 38, title: 'Standup ritual: overnight drift + todays plan', labels: ['est/3d', 'p/2'], milestone: 1, assignee: 'stephen',
events: [{ type: 'triage', at: '2026-02-25T09:00:00Z' }] },
{ number: 37, title: 'Offline write-queue with replay on reconnect', labels: ['est/5d', 'p/3'], milestone: 2, assignee: 'ana',
events: [{ type: 'triage', at: '2026-02-19T09:00:00Z' }] },
{ number: 36, title: 'Calibration from closed-issue actuals', labels: ['est/3d', 'p/3'], milestone: 2, assignee: 'stephen',
events: [{ type: 'triage', at: '2026-02-18T09:00:00Z' }] },
{ number: 35, title: 'Reginald: agent loop over query_project', labels: ['est/8d', 'p/2'], milestone: 2, assignee: 'ana',
events: [{ type: 'triage', at: '2026-02-17T09:00:00Z' }] },
{ number: 34, title: 'Board columns from lifecycle events', labels: [], // bare → diagnosis
events: [] },
{ number: 33, title: 'Deep-link URIs across orgs', labels: [], // bare → diagnosis
events: [] },
// shipped work — closed → done, feeds calibration actuals
{ number: 30, title: 'SQLite cache bootstrap', labels: ['est/3d', 'p/2'], milestone: 1, assignee: 'ana', state: 'closed',
createdAt: '2026-02-10T09:00:00Z', closedAt: '2026-02-14T17:00:00Z',
events: [{ type: 'triage', at: '2026-02-10T09:00:00Z' }, { type: 'commit', at: '2026-02-11T09:00:00Z' }, { type: 'pull', at: '2026-02-13T09:00:00Z' }, { type: 'close', at: '2026-02-14T17:00:00Z' }] },
{ number: 29, title: 'Connection + target-repo config', labels: ['est/2d', 'p/2'], milestone: 1, assignee: 'stephen', state: 'closed',
createdAt: '2026-02-09T09:00:00Z', closedAt: '2026-02-12T15:00:00Z',
events: [{ type: 'triage', at: '2026-02-09T09:00:00Z' }, { type: 'commit', at: '2026-02-10T09:00:00Z' }, { type: 'close', at: '2026-02-12T15:00:00Z' }] },
{ number: 24, title: 'Unified apply_changes mutation tool', labels: ['est/5d', 'p/1'], milestone: 1, assignee: 'ana', state: 'closed',
createdAt: '2026-02-05T09:00:00Z', closedAt: '2026-02-11T12:00:00Z',
events: [{ type: 'triage', at: '2026-02-05T09:00:00Z' }, { type: 'commit', at: '2026-02-06T09:00:00Z' }, { type: 'pull', at: '2026-02-10T09:00:00Z' }, { type: 'close', at: '2026-02-11T12:00:00Z' }] },
]
function toIssue(s: Seed): GiteaIssue {
const state = s.state ?? 'open'
return {
number: s.number,
title: s.title,
body: '',
state,
labels: s.labels,
facts: extractLabelFacts(s.labels),
milestone: s.milestone ? MREF(s.milestone) : null,
assignee: s.assignee ?? null,
assignees: s.assignee ? [s.assignee] : [],
createdAt: s.createdAt ?? '2026-02-20T09:00:00Z',
updatedAt: DEMO_TODAY,
closedAt: s.closedAt ?? null,
url: `${HOST}/${OWNER}/${REPO}/issues/${s.number}`,
}
}
export const DEMO_ISSUES: GiteaIssue[] = SEEDS.map(toIssue)
export const DEMO_MILESTONES: GiteaMilestone[] = MILESTONES_RAW.map((m) => ({
id: m.id,
title: m.title,
description: m.description,
dueOn: m.dueOn,
state: m.state,
openIssues: DEMO_ISSUES.filter((i) => i.milestone?.id === m.id && i.state === 'open').length,
closedIssues: DEMO_ISSUES.filter((i) => i.milestone?.id === m.id && i.state === 'closed').length,
}))
// #41 (forecast bands) depends on #39 (scheduler); #42 (lifecycle) depends on #34 (columns).
export const DEMO_DEPS: DependencyEdge[] = [
{ issue: 41, dependsOn: 39 },
{ issue: 42, dependsOn: 34 },
]
export const DEMO_TIMELINES: Record<number, LifecycleEvent[]> = Object.fromEntries(
SEEDS.map((s) => [s.number, s.events ?? []]),
)
export const DEMO_CAPACITY: CapacityMember[] = [
{ person: 'stephen', focusFactor: 0.6, allocation: 1 },
{ person: 'ana', focusFactor: 1, allocation: 0.8 },
]
export const DEMO_COLLABORATORS: { login: string; name: string }[] = [
{ login: 'stephen', name: 'Stephen Mann' },
{ login: 'ana', name: 'Ana Koval' },
]
export const DEMO_DIRECTIVES: DirectiveRecord[] = [
{ seq: 1, id: 'demo-1', ts: '2026-02-26T10:00:00Z', status: 'accepted', kind: 'reprioritize',
quote: 'Bump the lifecycle fix — its blocking the board.', target: { issue: 42 }, params: { priority: 1 },
rationale: 'Board columns are wrong until #42 lands.' },
{ seq: 2, id: 'demo-2', ts: '2026-02-24T15:00:00Z', status: 'accepted', kind: 'capacity',
quote: 'Stephens at 60% this sprint — half on support.', params: { person: 'stephen', focusFactor: 0.6 },
rationale: 'Support rotation.' },
{ seq: 3, id: 'demo-3', ts: '2026-02-22T09:00:00Z', status: 'amended', kind: 'set-deadline',
quote: 'Beta ships the 12th, hard.', target: { milestone: 1 }, params: { due: '2026-03-12' } },
]
/** The full gitea-shaped snapshot the reconcile handlers return in demo mode. */
export const DEMO_SNAPSHOT = {
issues: DEMO_ISSUES,
milestones: DEMO_MILESTONES,
deps: DEMO_DEPS,
timelines: DEMO_TIMELINES,
}