diff --git a/app/lab/start/page.tsx b/app/lab/start/page.tsx new file mode 100644 index 00000000..12a84183 --- /dev/null +++ b/app/lab/start/page.tsx @@ -0,0 +1,28 @@ +import type { Metadata } from 'next'; +import { FirstRunFlow } from '@/components/first-run-flow'; + +/** + * PUBLIC BETA. A guided walkthrough for three kinds of visitor, linked from the home page and kept out of search + * results while it is in beta. It was a production 404 until the owner authorized this launch on 2026-11-08. + */ +export const metadata: Metadata = { + title: 'Public beta: try, test, report', + robots: { index: false, follow: false }, +}; + +export default function LabStartPage() { + return ( +
+
+

+ Public beta. This walkthrough may change. The checks on this page use saved runs, and what you type stays in your browser. +

+

Check your AI's claims against the evidence.

+

+ TrustShell checks the kinds of claims it can read, such as whether a GitHub Actions run passed. It does not see everything your AI 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/app/page.tsx b/app/page.tsx index c9fbcc9e..1b9918c5 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -76,6 +76,9 @@ export default function Home() { Install TrustShell + + Try the guided walkthrough + See what it prints 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 + +

+ +

+ {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) => ( + + ))} +
+
+ +