diff --git a/NOTICE b/NOTICE index 43213fc..817d852 100644 --- a/NOTICE +++ b/NOTICE @@ -28,11 +28,11 @@ intersection with the above, to derive envs/wordle_five/words/answers.json. See envs/wordle_five/words/PROVENANCE.md for exactly how. -------------------------------------------------------------------------------- -Manrope — SIL Open Font License 1.1 -https://github.com/sharanda/manrope -Copyright 2019 The Manrope Project Authors. Licence text: public/fonts/OFL.txt -"Manrope" is a Reserved Font Name under that licence: a modified version of the -font may not be distributed under that name. +Geist and Geist Mono — SIL Open Font License 1.1 +https://github.com/vercel/geist-font +Copyright 2024 The Geist Project Authors. The sans and mono faces are embedded +as variable woff2 files via @fontsource-variable/geist and +@fontsource-variable/geist-mono (latin subset). Licence text: public/fonts/OFL.txt Lucide — ISC https://github.com/lucide-icons/lucide diff --git a/package.json b/package.json index 18762b2..026bf2b 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,8 @@ "conformance": "node scripts/conformance.mjs" }, "dependencies": { + "@fontsource-variable/geist": "^5.3.0", + "@fontsource-variable/geist-mono": "^5.3.0", "@fontsource-variable/manrope": "^5.3.0", "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-dialog": "^1.1.23", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 87d336c..c40293d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,12 @@ importers: .: dependencies: + '@fontsource-variable/geist': + specifier: ^5.3.0 + version: 5.3.0 + '@fontsource-variable/geist-mono': + specifier: ^5.3.0 + version: 5.3.0 '@fontsource-variable/manrope': specifier: ^5.3.0 version: 5.3.0 @@ -526,6 +532,12 @@ packages: '@floating-ui/utils@0.2.12': resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==} + '@fontsource-variable/geist-mono@5.3.0': + resolution: {integrity: sha512-vBbuwDEo9AkrqADMXOrlAR3DFcJi4/JxeuU43FoiQERnNwsfXNnvxvReZG02cQKmyk4DZkZdBZX3oTDvy2zBAw==} + + '@fontsource-variable/geist@5.3.0': + resolution: {integrity: sha512-j0m+vLQuG5XAYoHtGCVu0spvlGreR3EzpECUVzkFmI1mTVnAO38l/NEPDCFgZ177JxzYJCLSmTQibIiYPilGrA==} + '@fontsource-variable/manrope@5.3.0': resolution: {integrity: sha512-6D5dgokHsWDDMtmXHznKa0hK229NN+1a4BLPmUCLqcO1Pw5EEhWY5RFt0AcXnVRAljFFPfRtLkJePQj6LSsV6g==} @@ -2117,6 +2129,10 @@ snapshots: '@floating-ui/utils@0.2.12': {} + '@fontsource-variable/geist-mono@5.3.0': {} + + '@fontsource-variable/geist@5.3.0': {} + '@fontsource-variable/manrope@5.3.0': {} '@jridgewell/gen-mapping@0.3.13': diff --git a/public/fonts/OFL.txt b/public/fonts/OFL.txt index 462280f..98835ac 100644 --- a/public/fonts/OFL.txt +++ b/public/fonts/OFL.txt @@ -1,4 +1,4 @@ -Copyright 2019 The Manrope Project Authors (https://github.com/sharanda/manrope) +Copyright 2024 The Geist Project Authors (https://github.com/vercel/geist-font) Geist-Italic[wght].ttf: Copyright 2024 The Geist Project Authors (https://github.com/vercel/geist-font) This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: diff --git a/public/og/alert-triage.png b/public/og/alert-triage.png index a75a912..9ebc01a 100644 Binary files a/public/og/alert-triage.png and b/public/og/alert-triage.png differ diff --git a/public/og/demos.png b/public/og/demos.png index 664de01..0dcd292 100644 Binary files a/public/og/demos.png and b/public/og/demos.png differ diff --git a/public/og/gallery.png b/public/og/gallery.png index 664de01..0dcd292 100644 Binary files a/public/og/gallery.png and b/public/og/gallery.png differ diff --git a/public/og/home.png b/public/og/home.png index 220261c..7c0a9a5 100644 Binary files a/public/og/home.png and b/public/og/home.png differ diff --git a/public/og/honesty.png b/public/og/honesty.png index a833f4a..6ad07e8 100644 Binary files a/public/og/honesty.png and b/public/og/honesty.png differ diff --git a/public/og/vertical-code-fix-the-test.png b/public/og/vertical-code-fix-the-test.png index 39b9f7d..1170394 100644 Binary files a/public/og/vertical-code-fix-the-test.png and b/public/og/vertical-code-fix-the-test.png differ diff --git a/public/og/vertical-customer-support-resolution.png b/public/og/vertical-customer-support-resolution.png index e339f25..2ae1ba8 100644 Binary files a/public/og/vertical-customer-support-resolution.png and b/public/og/vertical-customer-support-resolution.png differ diff --git a/public/og/vertical-data-column-split.png b/public/og/vertical-data-column-split.png index ef52656..6030d6a 100644 Binary files a/public/og/vertical-data-column-split.png and b/public/og/vertical-data-column-split.png differ diff --git a/public/og/vertical-energy-day-ahead-bid.png b/public/og/vertical-energy-day-ahead-bid.png index 6969fac..f05f513 100644 Binary files a/public/og/vertical-energy-day-ahead-bid.png and b/public/og/vertical-energy-day-ahead-bid.png differ diff --git a/public/og/vertical-financial-crime-alert-triage.png b/public/og/vertical-financial-crime-alert-triage.png index 86ffe69..b203b60 100644 Binary files a/public/og/vertical-financial-crime-alert-triage.png and b/public/og/vertical-financial-crime-alert-triage.png differ diff --git a/public/og/vertical-healthcare-denial-appeal.png b/public/og/vertical-healthcare-denial-appeal.png index 2e7b089..a1189cc 100644 Binary files a/public/og/vertical-healthcare-denial-appeal.png and b/public/og/vertical-healthcare-denial-appeal.png differ diff --git a/public/og/vertical-insurance-coverage-reserve.png b/public/og/vertical-insurance-coverage-reserve.png index 248afba..8693a77 100644 Binary files a/public/og/vertical-insurance-coverage-reserve.png and b/public/og/vertical-insurance-coverage-reserve.png differ diff --git a/public/og/vertical-legal-playbook-redline.png b/public/og/vertical-legal-playbook-redline.png index ebf7a7d..7c788c9 100644 Binary files a/public/og/vertical-legal-playbook-redline.png and b/public/og/vertical-legal-playbook-redline.png differ diff --git a/public/og/vertical-logistics-load-and-reroute.png b/public/og/vertical-logistics-load-and-reroute.png index f59cd2b..d520bc7 100644 Binary files a/public/og/vertical-logistics-load-and-reroute.png and b/public/og/vertical-logistics-load-and-reroute.png differ diff --git a/public/og/vertical-retail-markdown-cadence.png b/public/og/vertical-retail-markdown-cadence.png index fa20a35..a55e65c 100644 Binary files a/public/og/vertical-retail-markdown-cadence.png and b/public/og/vertical-retail-markdown-cadence.png differ diff --git a/public/og/vertical-semiconductor-ppa-closure.png b/public/og/vertical-semiconductor-ppa-closure.png index 2b62a50..c807143 100644 Binary files a/public/og/vertical-semiconductor-ppa-closure.png and b/public/og/vertical-semiconductor-ppa-closure.png differ diff --git a/public/og/vertical-telecom-alarm-root-cause.png b/public/og/vertical-telecom-alarm-root-cause.png index cd39409..82dd5e4 100644 Binary files a/public/og/vertical-telecom-alarm-root-cause.png and b/public/og/vertical-telecom-alarm-root-cause.png differ diff --git a/public/og/wordle.png b/public/og/wordle.png index c4fc88b..5d8e1f6 100644 Binary files a/public/og/wordle.png and b/public/og/wordle.png differ diff --git a/scripts/check-licenses.mjs b/scripts/check-licenses.mjs index 09cbc42..b1eae38 100644 --- a/scripts/check-licenses.mjs +++ b/scripts/check-licenses.mjs @@ -4,14 +4,14 @@ * * This repo is public and Apache-2.0, and almost none of what makes the demo * work is ours: the primitives are shadcn/ui over Radix, the answer list is - * Wordnik's filtered through SCOWL, the typeface is Manrope under the SIL Open - * Font License, the icons are lucide, the charts are recharts. Every one of + * Wordnik's filtered through SCOWL, the typefaces are Geist and Geist Mono + * under the SIL Open Font License, the icons are lucide, the charts are recharts. Every one of * those licences is permissive, and every one of them requires attribution. * * So `NOTICE` is not paperwork, it is a build artifact with a test. This script * is that test: it fails if NOTICE is missing any source we actually ship, and * it fails if a source is named without its licence beside it — because - * "uses Manrope" without "OFL-1.1" is not attribution, it is a mention. + * "uses Geist" without "OFL-1.1" is not attribution, it is a mention. * * The OFL additionally requires that the licence text travel WITH the font, so * `public/fonts/OFL.txt` is checked for separately. Shipping the .woff2 out of @@ -64,13 +64,22 @@ const SOURCES = [ used: () => hasWordFile(/scowl/i), }, { - label: 'Manrope', - match: /manrope/i, + label: 'Geist', + match: /\bgeist\b/i, // The OFL is version-specific and the version matters: OFL-1.1 is the one - // Manrope ships under, and it is the one whose terms are quoted in OFL.txt. + // Geist ships under, and it is the one whose terms are quoted in OFL.txt. licence: /OFL[-\s]?1\.1|SIL\s+Open\s+Font\s+License/i, - why: 'Manrope is the typeface, embedded as a variable woff2.', - used: () => exists(abs('node_modules', '@fontsource-variable', 'manrope')), + why: 'Geist is the sans typeface, embedded as a variable woff2.', + used: () => exists(abs('node_modules', '@fontsource-variable', 'geist')), + }, + { + label: 'Geist Mono', + // A separate package with its own LICENSE file, so it is a separate entry: + // naming the sans face does not attribute the mono one. + match: /geist\s+mono|geist-mono/i, + licence: /OFL[-\s]?1\.1|SIL\s+Open\s+Font\s+License/i, + why: 'Geist Mono is the mono typeface, embedded as a variable woff2.', + used: () => exists(abs('node_modules', '@fontsource-variable', 'geist-mono')), }, { label: 'lucide', @@ -162,8 +171,8 @@ report.check( exists(OFL), rel(OFL), 'OFL text ships with the font', - 'Manrope is under SIL OFL-1.1, which requires the licence text to travel with the font files. ' + - 'Copy node_modules/@fontsource-variable/manrope/LICENSE to public/fonts/OFL.txt. ' + + 'Geist and Geist Mono are under SIL OFL-1.1, which requires the licence text to travel with the font files. ' + + 'Copy node_modules/@fontsource-variable/geist/LICENSE to public/fonts/OFL.txt. ' + 'Shipping the woff2 and leaving the licence behind is the one violation on this list with teeth.', ); diff --git a/src/components/demo/BlindCompare.tsx b/src/components/demo/BlindCompare.tsx index 561871d..e244bf4 100644 --- a/src/components/demo/BlindCompare.tsx +++ b/src/components/demo/BlindCompare.tsx @@ -1,9 +1,10 @@ import { useState } from 'react'; import type { ComponentType } from 'react'; -import { Eye, Trophy } from 'lucide-react'; +import { Eye } from 'lucide-react'; import type { DemoStep } from '@/lib/demo-kit/types'; import { Button } from '@/components/ui/button'; import { cn } from '@/lib/utils'; +import { InterventionChip, Label } from './chrome'; import { formatOrDash } from './format'; export interface BlindCompareRun { @@ -39,6 +40,9 @@ export interface BlindCompareProps { * unknow it, and every "obviously the trained one looks better" is worthless * after the fact. * + * The two vote buttons are OUTLINE, not green: two green fills on the Watch + * tab would compete with ▶, and a vote is a question, not a call to action. + * * There is a "reveal without voting" escape on purpose: a visitor who does not * want to play should not be held hostage by a modal-shaped page. */ @@ -77,63 +81,82 @@ export function BlindCompare({ return (
-

{question}

-

Same puzzle, same seed ({seed}).

+

{question}

+

+ Same puzzle · seed {seed} +

{sides.map(({ id, run }) => { const last = run.steps[run.steps.length - 1]; const picked = vote === id; + const won = revealed && winner === id; return (
-

Agent {id}

- {run.steps.length} steps + + + {run.steps.length} steps +
-
+
{last ? ( - +
+ +
) : ( -

This run recorded no steps.

+

This run recorded no steps.

)}
{!revealed ? ( - ) : ( -
+
Identity
-
{run.label}
+
{run.label}
Model
-
{run.model}
+
+ {run.model} +
Intervention
-
- {run.intervention ?? ( - none — plain rollout +
+ {run.intervention ? ( + {run.intervention} + ) : ( + none — plain rollout )}
Reward
-
- {revealed && winner === id ? ( -
+ {won ? ( + + higher + ) : null} {formatOrDash(run.total)}
@@ -150,24 +173,25 @@ export function BlindCompare({ -
) : ( -

+

{vote === null ? ( <>Revealed without a vote. ) : vote === winner ? ( <> - You picked the higher-scoring run.{' '} + You picked the higher-scoring run.{' '} ) : ( <> - - You picked the lower-scoring run. - {' '} + You picked the lower-scoring run.{' '} )} The environment scored these two with the same grader, on the same seed. The difference diff --git a/src/components/demo/CodeReceipt.tsx b/src/components/demo/CodeReceipt.tsx index 34db242..ed24121 100644 --- a/src/components/demo/CodeReceipt.tsx +++ b/src/components/demo/CodeReceipt.tsx @@ -1,8 +1,7 @@ import { useMemo, useState } from 'react'; -import { ExternalLink } from 'lucide-react'; import { Button } from '@/components/ui/button'; -import * as st from '@/content/styles'; import { cn } from '@/lib/utils'; +import { External } from './chrome'; export interface CodeReceiptProps { /** The file's text, imported with `?raw` so it cannot drift from the source. */ @@ -14,7 +13,7 @@ export interface CodeReceiptProps { * See `resolveMarkedRange` for the two conventions it supports. */ marker?: string; - /** Link to the whole file — GitHub, usually. */ + /** Link to the whole file in the repository. */ href?: string; /** Lines of context kept around the marked range while collapsed. */ context?: number; @@ -102,9 +101,9 @@ export function resolveMarkedRange(lines: string[], marker?: string): MarkedRang * that says "this, right here, is the grader" — costs nothing and is the whole * reason the panel exists. * - * The block scrolls horizontally inside itself. Long Python lines must never - * make the PAGE scroll sideways; on a phone that turns every vertical swipe - * into a fight. + * The block scrolls on both axes inside its own box. Long Python lines must + * never make the PAGE scroll sideways; on a phone that turns every vertical + * swipe into a fight. */ export function CodeReceipt({ code, @@ -130,42 +129,43 @@ export function CodeReceipt({ const gutterWidth = `${String(lines.length).length + 1}ch`; return ( -

-
+
+
{/* Full width on a phone: sharing a row with two controls truncates a path to "envs/wo…", which names nothing. */} -

+

{path}

- {range && !expanded ? ( - - ) : null} - {range && expanded ? ( - ) : null} {href ? ( - Read the whole file - ) : null}
-
-
+      
+
           
             {shown.from > 0 ? (
               
); } -/** - * Marks a number the visitor caused rather than one we recorded. It appears on - * every derived value in the reward editor; without it, an edited ranking - * screenshots identically to a measured one. - */ -export function EditedChip({ className }: { className?: string }) { - return ( - - edited - - ); -} +// Kept as a re-export so nothing that imported the chip from here breaks; the +// chip itself lives with the other chips in chrome.tsx. +export { EditedChip } from './chrome'; diff --git a/src/components/demo/StepTimeline.tsx b/src/components/demo/StepTimeline.tsx index 6893b1d..7d99e35 100644 --- a/src/components/demo/StepTimeline.tsx +++ b/src/components/demo/StepTimeline.tsx @@ -97,7 +97,9 @@ export function StepTimeline({ aria-label="Steps in the recorded run" aria-keyshortcuts="ArrowLeft ArrowRight Home End Space" onKeyDown={handleKeyDown} - className={cn('flex snap-x gap-2 overflow-x-auto pb-2', className)} + // One of the five horizontal scrollers §11 permits. The visible rule + // underneath says "this strip ends here" where a scrollbar may not show. + className={cn('flex snap-x gap-2 overflow-x-auto border-b border-border pb-3', className)} > {steps.map((step, index) => { const selected = index === current; @@ -118,17 +120,22 @@ export function StepTimeline({ }} onClick={() => onSelect(index)} className={cn( - 'tap w-[9.5rem] shrink-0 snap-center rounded-lg border p-2 text-left transition-colors duration-2 ease-enter', + 'tap w-[8rem] shrink-0 snap-center rounded-lg border p-2 text-left transition-colors duration-2 ease-enter sm:w-[9rem]', + 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-bg', + // `accent-edge` is the green that reads as a border in both + // themes; the raw accent is 1.47:1 against white. selected - ? 'border-brand bg-accent-subtle/60' + ? 'border-accent-edge bg-accent-subtle' : 'border-border bg-surface hover:bg-surface-2', )} > - Step {index + 1} + + Step {index + 1} + {call?.finishReason ? ( {humaniseToken(call.finishReason)} @@ -143,12 +150,14 @@ export function StepTimeline({ ) : null} {step.caption ? ( - + // fg-2, not muted: on the selected chip's accent-subtle ground + // muted is 4.93 in dark, and a caption is a sentence. + {step.caption} ) : null} - + {call?.completionTokens === null || call?.completionTokens === undefined ? DASH diff --git a/src/components/demo/TracePlayer.tsx b/src/components/demo/TracePlayer.tsx index aa81177..588f624 100644 --- a/src/components/demo/TracePlayer.tsx +++ b/src/components/demo/TracePlayer.tsx @@ -1,15 +1,16 @@ -import { ChevronLeft, ChevronRight, Circle, Pause, Play, RotateCcw } from 'lucide-react'; +import { ChevronLeft, ChevronRight, Pause, Play, RotateCcw } from 'lucide-react'; import { PLAYBACK_SPEEDS } from '@/lib/demo-kit/player'; import type { PlaybackSpeed } from '@/lib/demo-kit/player'; import { Button } from '@/components/ui/button'; import { cn } from '@/lib/utils'; +import { Chip, InterventionChip } from './chrome'; import { formatDate } from './format'; import { SegmentedControl } from './SegmentedControl'; /** `PlaybackSpeed` is `1 | 2 | 4 | 'instant'`; the control speaks strings. */ const SPEED_OPTIONS = PLAYBACK_SPEEDS.map((speed) => ({ value: String(speed), - label: speed === 'instant' ? 'Instant' : `${speed}x`, + label: speed === 'instant' ? 'INSTANT' : `${speed}×`, title: speed === 'instant' ? 'Jump straight to the end of the run' : `${speed} times real pace`, })); @@ -50,6 +51,9 @@ export interface TracePlayerProps { * believes they are watching a model think in real time has been misled by the * UI rather than the copy. Hence the permanent badge — not a disclosure tucked * into a footnote, but a fixture of the transport bar for the whole session. + * + * ▶ is the only green fill on the Watch tab. Everything else in the bar is a + * ghost, so the one thing that starts the run is the one thing that is lit. */ export function TracePlayer({ playing, @@ -70,7 +74,7 @@ export function TracePlayer({ const canPlay = stepCount > 1; return ( -
+
-

- Step {Math.min(step + 1, stepCount)} of{' '} +

+ Step{' '} + {Math.min(step + 1, stepCount)} + + of {stepCount}

@@ -120,6 +127,7 @@ export function TracePlayer({ options={SPEED_OPTIONS} value={String(speed)} onChange={(next) => onSpeedChange(toSpeed(next))} + optionClassName="px-2.5 font-mono text-caption tracking-label" />
@@ -135,10 +144,12 @@ export function TracePlayer({ exists so the pause between steps reads as time passing rather than as the page having stopped. `transition-[width]` covers the ~15 Hz at which the player pushes progress — without it the bar visibly ratchets. + `tab-underline` is the token that is accent in dark and accent-fg in + light; a state indicator on a surface-3 track needs exactly that split. */} -