From 88608b7fc72948f24717bb0ca9401eb08ee03daf Mon Sep 17 00:00:00 2001 From: Sean Goodwin Date: Sun, 8 Nov 2026 21:27:28 -0800 Subject: [PATCH 1/2] feat(site): first-run preview at /lab/start: try, test, report Three paths through one journey, preview only: a 404 on production, not linked, not indexed. The home page and existing routes are unchanged. The chat path runs the real claim checker in the browser against two saved GitHub runs, with no key and nothing sent. The GitHub path and the developer path show commands that were run against the published 1.9.1 in an empty folder with no keys, in PowerShell and in bash. Reuses the guided page's components, which now export what this needs. NOT_CHECKED now renders neutral and COMMAND_PASSED dashed, on both pages. The shared example report is regenerated with 1.9.1. Co-Authored-By: Claude Opus 5.5 --- app/lab/start/page.tsx | 28 ++ components/first-run-chat.tsx | 268 ++++++++++++++ components/first-run-flow.tsx | 304 ++++++++++++++++ components/test-agent-flow.tsx | 120 ++++--- lib/first-run-check.ts | 50 +++ lib/first-run.ts | 81 +++++ lib/trust-report-view.ts | 13 +- public/examples/run-failed-record.json | 116 +++++++ public/examples/run-passed-record.json | 116 +++++++ public/examples/trust-report-example.json | 40 ++- tests/e2e/first-run-walk.mjs | 404 ++++++++++++++++++++++ tests/first-run.test.ts | 130 +++++++ 12 files changed, 1611 insertions(+), 59 deletions(-) create mode 100644 app/lab/start/page.tsx create mode 100644 components/first-run-chat.tsx create mode 100644 components/first-run-flow.tsx create mode 100644 lib/first-run-check.ts create mode 100644 lib/first-run.ts create mode 100644 public/examples/run-failed-record.json create mode 100644 public/examples/run-passed-record.json create mode 100644 tests/e2e/first-run-walk.mjs create mode 100644 tests/first-run.test.ts diff --git a/app/lab/start/page.tsx b/app/lab/start/page.tsx new file mode 100644 index 00000000..6d42c233 --- /dev/null +++ b/app/lab/start/page.tsx @@ -0,0 +1,28 @@ +import type { Metadata } from 'next'; +import { notFound } from 'next/navigation'; +import { FirstRunFlow } from '@/components/first-run-flow'; + +/** + * PREVIEW ONLY, like /lab/test-agent. A first-run journey for three kinds of visitor: a 404 on production, not + * linked, not indexed. The current home page and every existing route are unchanged. + */ +export const metadata: Metadata = { + title: 'Preview: try, test, report', + robots: { index: false, follow: false }, +}; + +export default function LabStartPage() { + if (process.env.VERCEL_ENV === 'production') notFound(); + return ( +
+
+

+ Preview. This page is not live on trustshell.dev. +

+

Check what your AI did, not what it says it did.

+

Try it on a saved example, test it on your own work, then read the report. No account and no key.

+ +
+
+ ); +} diff --git a/components/first-run-chat.tsx b/components/first-run-chat.tsx new file mode 100644 index 00000000..d50891e5 --- /dev/null +++ b/components/first-run-chat.tsx @@ -0,0 +1,268 @@ +'use client'; + +import { useEffect, useRef, useState, type ReactNode } from 'react'; +import { ChevronRight } from 'lucide-react'; +import { PRIMARY, SECONDARY, Status } from '@/components/test-agent-flow'; +import type { Go, StepId } from '@/components/first-run-flow'; +import { EXIT_MEANING, RUNS, SENTENCES, STATUS_MEANING, STATUS_WORDS, type RunId } from '@/lib/first-run'; +import type { SentenceResult } from '@/lib/first-run-check'; + +type Checked = { text: string; run: RunId; result: SentenceResult; own: boolean }; + +/** Said where it matters, in the same words: the limit a visitor is most likely to assume away. */ +export function Limit({ children }: { children: ReactNode }) { + return

{children}

; +} + +export function Legend() { + return ( +
+ {STATUS_WORDS.map((w) => ( +
+
+ +
+
{STATUS_MEANING[w]}
+
+ ))} +
+ ); +} + +export function LegendToggle() { + return ( +
+ + +
+ +
+
+ ); +} + +/** The checker and both saved runs, or null if either could not be fetched. */ +async function prepare() { + try { + const lib = await import('@/lib/first-run-check'); + const runs = await lib.loadSavedRuns(); + return runs ? { runs, checkSentence: lib.checkSentence } : null; + } catch { + return null; + } +} + +const INPUT = 'w-full rounded-lg border border-slate-600 bg-slate-950 px-3 py-2 text-sm text-white'; + +function Receipt({ checked, full }: { checked: Checked; full?: boolean }) { + const { record, tally, exit } = checked.result; + const o = record.observation; + const heading = useRef(null); + // A new result takes focus so it is announced and scrolled into view; opening the Report step does not need it. + useEffect(() => { + if (!full) heading.current?.focus(); + }, [checked, full]); + return ( +
+

+ What the check found +

+

“{checked.text}”

+

+ Compared with GitHub’s record of run {o.run_id} in {o.owner}/{o.repo}, saved {o.fetchedAt.slice(0, 10)}. GitHub reports it finished with conclusion {o.conclusion ?? 'none'}.{' '} + + Open this run on GitHub + +

+
    + {record.claims.map((c, i) => ( +
  • + {c.claim} + {c.reason} + {full ? Covers: {c.scope} : null} +
  • + ))} +
+

+ {tally.SUPPORTED} supported, {tally.CONTRADICTED} contradicted, {tally.NOT_CHECKED} not checked. {EXIT_MEANING[exit]} The command line would exit {exit}. +

+ {full ? ( +
+

What this does not prove

+
    + {record.does_not_prove.map((t) => ( +
  • {t}
  • + ))} +
+
+ ) : null} +
+ ); +} + +export function ChatPath({ step, go, toVibe }: { step: StepId; go: Go; toVibe: () => void }) { + const [ready, setReady] = useState>>(null); + const [state, setState] = useState<'loading' | 'ready' | 'error'>('loading'); + const [checked, setChecked] = useState(null); + const [run, setRun] = useState('failed'); + const [text, setText] = useState(''); + const [empty, setEmpty] = useState(false); + + useEffect(() => { + let live = true; + void prepare().then((r) => { + if (!live) return; + setReady(r); + setState(r ? 'ready' : 'error'); + }); + return () => { + live = false; + }; + }, []); + + const retry = async () => { + setState('loading'); + const r = await prepare(); + setReady(r); + setState(r ? 'ready' : 'error'); + }; + + const check = (sentence: string, on: RunId, own: boolean) => { + if (ready) setChecked({ text: sentence, run: on, result: ready.checkSentence(sentence, ready.runs[on]), own }); + }; + + const submit = () => { + const sentence = text.trim(); + setEmpty(!sentence); + if (sentence) check(sentence, run, true); + }; + + if (state !== 'ready' && step !== 'report') { + return state === 'loading' ? ( +

+ Loading the saved runs… +

+ ) : ( +
+
+ The saved runs could not be loaded. Check your connection and try again. +
+ +
+ ); + } + + if (step === 'try') { + const shown = checked && !checked.own ? checked : null; + return ( +
+

These are things an AI might say about real runs of an automated check on GitHub. Pick one. The checker the TrustShell command line uses runs here in your browser, against GitHub’s record of that run.

+ TrustShell cannot see your conversations with an AI. It sees only what goes through it. On this page that means the sentence you pick or type, and it stays in your browser. +
    + {SENTENCES.map((s) => ( +
  • + +
  • + ))} +
+ {shown ? :

Pick a sentence to see what the record shows.

} + + +
+ ); + } + + if (step === 'test') { + const shown = checked?.own ? checked : null; + return ( +
+
+ Which run is it about? + {(['failed', 'passed'] as const).map((id) => ( + + ))} +
+
+ +