From 38dc72620a05a0affb88b1c0fe5976f491597bd2 Mon Sep 17 00:00:00 2001
From: panshulthinks
Date: Mon, 21 Sep 2026 00:06:17 +0530
Subject: [PATCH 1/7] feat(frontend): refine overview and project messaging
---
frontend/.gitignore | 4 +
frontend/ARCHITECTURE.md | 291 ++
frontend/DESIGN.md | 405 +++
frontend/README.md | 46 +
frontend/index.html | 22 +
frontend/package-lock.json | 3070 +++++++++++++++++
frontend/package.json | 29 +
frontend/public/data/README.md | 2 +
frontend/src/App.tsx | 28 +
frontend/src/content/copy.ts | 164 +
frontend/src/data/adapters/snapshotAdapter.ts | 31 +
frontend/src/data/contracts.ts | 124 +
frontend/src/data/fixtures/empty-checks.json | 22 +
.../src/data/fixtures/missing-parent.json | 96 +
.../src/data/fixtures/public-snapshot.json | 231 ++
frontend/src/data/fixtures/superseded.json | 136 +
frontend/src/data/validate.ts | 284 ++
frontend/src/inference/DisabledAdapter.ts | 17 +
frontend/src/inference/MockAdapter.ts | 40 +
frontend/src/inference/types.ts | 41 +
frontend/src/layout/AppShell.tsx | 91 +
frontend/src/layout/ClaimStrip.tsx | 26 +
frontend/src/layout/StatusMark.tsx | 26 +
frontend/src/main.tsx | 13 +
frontend/src/nav.ts | 7 +
frontend/src/pages/EvidenceDetailPage.tsx | 138 +
frontend/src/pages/EvidenceListPage.tsx | 196 ++
frontend/src/pages/Journey.tsx | 61 +
frontend/src/pages/OverviewPage.tsx | 75 +
frontend/src/pages/ReleasesPage.tsx | 63 +
frontend/src/pages/TryPage.tsx | 45 +
frontend/src/pages/VerifyPage.tsx | 109 +
frontend/src/snapshot.tsx | 53 +
frontend/src/styles/base.css | 675 ++++
frontend/src/styles/tokens.css | 20 +
frontend/src/vite-env.d.ts | 1 +
frontend/tests/adapter.test.ts | 38 +
frontend/tests/journey.test.ts | 16 +
frontend/tests/validate.test.ts | 69 +
frontend/tsconfig.app.json | 24 +
frontend/tsconfig.json | 7 +
frontend/tsconfig.node.json | 21 +
frontend/vite.config.ts | 11 +
43 files changed, 6868 insertions(+)
create mode 100644 frontend/.gitignore
create mode 100644 frontend/ARCHITECTURE.md
create mode 100644 frontend/DESIGN.md
create mode 100644 frontend/README.md
create mode 100644 frontend/index.html
create mode 100644 frontend/package-lock.json
create mode 100644 frontend/package.json
create mode 100644 frontend/public/data/README.md
create mode 100644 frontend/src/App.tsx
create mode 100644 frontend/src/content/copy.ts
create mode 100644 frontend/src/data/adapters/snapshotAdapter.ts
create mode 100644 frontend/src/data/contracts.ts
create mode 100644 frontend/src/data/fixtures/empty-checks.json
create mode 100644 frontend/src/data/fixtures/missing-parent.json
create mode 100644 frontend/src/data/fixtures/public-snapshot.json
create mode 100644 frontend/src/data/fixtures/superseded.json
create mode 100644 frontend/src/data/validate.ts
create mode 100644 frontend/src/inference/DisabledAdapter.ts
create mode 100644 frontend/src/inference/MockAdapter.ts
create mode 100644 frontend/src/inference/types.ts
create mode 100644 frontend/src/layout/AppShell.tsx
create mode 100644 frontend/src/layout/ClaimStrip.tsx
create mode 100644 frontend/src/layout/StatusMark.tsx
create mode 100644 frontend/src/main.tsx
create mode 100644 frontend/src/nav.ts
create mode 100644 frontend/src/pages/EvidenceDetailPage.tsx
create mode 100644 frontend/src/pages/EvidenceListPage.tsx
create mode 100644 frontend/src/pages/Journey.tsx
create mode 100644 frontend/src/pages/OverviewPage.tsx
create mode 100644 frontend/src/pages/ReleasesPage.tsx
create mode 100644 frontend/src/pages/TryPage.tsx
create mode 100644 frontend/src/pages/VerifyPage.tsx
create mode 100644 frontend/src/snapshot.tsx
create mode 100644 frontend/src/styles/base.css
create mode 100644 frontend/src/styles/tokens.css
create mode 100644 frontend/src/vite-env.d.ts
create mode 100644 frontend/tests/adapter.test.ts
create mode 100644 frontend/tests/journey.test.ts
create mode 100644 frontend/tests/validate.test.ts
create mode 100644 frontend/tsconfig.app.json
create mode 100644 frontend/tsconfig.json
create mode 100644 frontend/tsconfig.node.json
create mode 100644 frontend/vite.config.ts
diff --git a/frontend/.gitignore b/frontend/.gitignore
new file mode 100644
index 00000000..a4d699a9
--- /dev/null
+++ b/frontend/.gitignore
@@ -0,0 +1,4 @@
+node_modules
+dist
+*.local
+.DS_Store
diff --git a/frontend/ARCHITECTURE.md b/frontend/ARCHITECTURE.md
new file mode 100644
index 00000000..fd87b596
--- /dev/null
+++ b/frontend/ARCHITECTURE.md
@@ -0,0 +1,291 @@
+# Frontend architecture
+
+Maps `FRONTEND_CONTRIBUTOR_BRIEF.md` onto a static app we can merge.
+Visual language lives in `DESIGN.md`. This file is structure, data, and
+what “crazy” is allowed to mean.
+
+---
+
+## 1. Research: how AOSSIE actually ships frontends
+
+What mentors already reward in this org (GSoC 2025–2026):
+
+| Project | What it is | Frontend pattern | Lesson for us |
+| --- | --- | --- | --- |
+| **OrgExplorer** | GitHub org analytics | Vite + React + TS + Tailwind, **no backend**, GitHub Pages, IndexedDB cache | Sunny/less-cloud. Browser is the runtime. |
+| **PictoPy** | Desktop gallery | React + Vite + Tailwind + ShadCN *inside Tauri*; separate static landing on `pictopy.aossie.org` | App UI ≠ marketing site. Landing is static Pages. |
+| **PictoPy-Website / Resonate-Website / DebateAI-LandingPage** | Project landings | Vite/React, GH Pages, `*.aossie.org` | Org wants static landings, not Vercel. |
+| **AOSSIE Website** | Org home | Next.js 16 + Tailwind v4 | Exception: the org site. **Our brief forbids a backend.** Do not copy Next. |
+| **Devr.AI** | DevRel agent | React dashboard + FastAPI | Cloud-heavy. Opposite of this assignment. |
+| **LandingPages idea (GSoC 2026)** | Bruno/M4dhav | Static pages, SEO, **better than generic AI landings** | Mentors have said out loud they can smell AI templates. |
+| **UI idea** | Same mentors | Clean, minimal, **easy to maintain next year** | No throwaway Aceternity theme. |
+
+Shared stack we should match: **React + TypeScript + Vite + static host**.
+Shared philosophy: no paid APIs, no login required, GitHub Pages.
+
+What they do **not** have: a site whose job is *refusing to overclaim*.
+That is our opening. OrgExplorer graphs GitHub. PictoPy shows photos.
+We show **a chain of checks that can be broken**. If we do that with
+restraint, it is the most distinctive frontend in the org this year.
+
+---
+
+## 2. Product, in one sentence
+
+A **static evidence docket**: public snapshot in, honest pages out.
+It never trains, never signs, never calls GPU, never pretends a model exists.
+
+```
+approved JSON snapshot → validate → view models → pages
+ ↑ ↓
+ fixtures (dev) fail closed (no mock fallback)
+```
+
+The training pipeline (`src/`, PR #101, RunPod) is a different program.
+We only *display* what the maintainer allows.
+
+---
+
+## 3. Runtime architecture
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ Browser (GitHub Pages / local Vite) │
+│ │
+│ HashRouter ─ no server rewrites needed │
+│ │
+│ Layout │
+│ nav · ClaimStrip · · footer │
+│ │
+│ SnapshotProvider │
+│ mode: fixture | public-snapshot │
+│ load → parse → validate → freeze │
+│ on error: ErrorView (never swap in a happy fixture) │
+│ │
+│ Pages │
+│ / Overview │
+│ /evidence list + filters │
+│ /evidence/:id detail + chain rail │
+│ /releases base + chat cards │
+│ /verify five profiles │
+│ /try inference shell │
+│ │
+│ InferenceGateway │
+│ public build → DisabledAdapter │
+│ ?fixture=1 → MockAdapter + sticky warning │
+└─────────────────────────────────────────────────────────────┘
+```
+
+No React Query polling. No IndexedDB of visitor chats. OrgExplorer uses
+IndexedDB because it *fetches GitHub*. We ship the snapshot **in the
+build** (`public/data/`). Optional later: fetch one allowlisted URL with
+integrity, still fail-closed.
+
+**HashRouter** is the static-safe strategy the brief asks for. Direct
+load of `/#/evidence/xyz` works on GitHub Pages without a 404.html hack.
+
+---
+
+## 4. Directory (implement this, not a larger tree)
+
+```
+frontend/
+ README.md
+ DESIGN.md
+ ARCHITECTURE.md
+ package.json
+ package-lock.json
+ index.html
+ vite.config.ts # base: './' until maintainer sets Pages path
+ tsconfig.json
+ src/
+ main.tsx
+ App.tsx # providers + routes
+ layout/
+ AppShell.tsx
+ ClaimStrip.tsx
+ StatusMark.tsx
+ pages/
+ OverviewPage.tsx
+ EvidenceListPage.tsx
+ EvidenceDetailPage.tsx
+ ReleasesPage.tsx
+ VerifyPage.tsx
+ TryPage.tsx
+ ErrorPage.tsx
+ data/
+ contracts.ts # types from brief §7.2
+ validate.ts # runtime checks, fail closed
+ adapters/
+ snapshotAdapter.ts
+ fixtures/ # labelled synthetic JSON (imported by tests too)
+ inference/
+ types.ts
+ DisabledAdapter.ts
+ MockAdapter.ts
+ styles/
+ tokens.css
+ base.css
+ public/
+ data/
+ public-snapshot.json # mode labelled; may be partial
+ README.md # how to refresh + revision
+ tests/
+ validate.test.ts
+ status.test.ts
+ releases.test.ts
+ adapter.test.ts
+ navigation.test.ts # small browser test for unreleased flow
+```
+
+Do not add Tailwind/ShadCN/Aceternity unless a later PR needs it.
+Ordinary CSS + the token table in `DESIGN.md` is enough and is closer
+to “maintainable next year” than a UI kit.
+
+---
+
+## 5. Data contract (brief §7, implemented)
+
+One versioned snapshot is the only store.
+
+```ts
+type CheckResult = 'PASS' | 'FAIL' | 'NOT_RUN' | 'UNAVAILABLE' | 'UNSUPPORTED';
+type EvidenceScope = 'fixture' | 'pilot' | 'production';
+type DataMode = 'fixture' | 'public-snapshot';
+type ReleaseAvailability = 'not-released' | 'available' | 'withdrawn';
+```
+
+`validate.ts` must:
+
+- reject unknown schema versions as **data error**, not PASS
+- map only known legacy aliases (`passed` → PASS, `pending` → NOT_RUN)
+- never coerce `"ok"` / `"complete"` / `"true"` to PASS
+- require: snapshot mode, generatedAt, source refs, checks[], releases[]
+- require each PASS to have profile, scope, explanation, evidence refs
+- mark missing mandatory fields UNAVAILABLE or throw `SnapshotError`
+
+Adapters keep `url`, `revision`, `path`, `sha256` as they came.
+Hashes are displayed as the source string. We do **not** JSON.parse and
+re-stringify then call that “the same bytes.”
+
+`locallyRecomputed` is **always false** in this static app. Showing a
+publisher JSON is not a local replay.
+
+Failed public fetch: `ErrorPage`. No silent fixture.
+
+---
+
+## 6. Pages ↔ brief (everything must land)
+
+| Brief | Module | Behaviour that cannot be skipped |
+| --- | --- | --- |
+| §5.1 Overview | `OverviewPage` | Purpose, **not released**, 3 actions, vertical pipeline with real statuses, provenance ≠ truth. No % complete, no fake date, no hero chat. |
+| §5.2 Evidence | `EvidenceListPage` | Table/list primary. Filters: phase, kind, result, scope. Size + link, never auto-download dumps. |
+| §5.2 Detail | `EvidenceDetailPage` | Digest copy, parents/children, missing parent as gap, superseded kept, bounded escaped JSON. |
+| §5.3 Releases | `ReleasesPage` | Two cards. Disabled download/generate until metadata. No invented HF names. |
+| §5.4 Verify | `VerifyPage` | Five profiles, distinct meanings. CLI = “Instructions pending release” until ryoari pastes a tested command. |
+| §5.5 Try | `TryPage` + adapters | Public: generation unavailable. Fixture: labelled mock. Reject identity mismatch. No logging. |
+| §6 Status | `StatusMark` | PASS/FAIL/NOT_RUN/UNAVAILABLE/UNSUPPORTED + text, not color-only. Empty checks ≠ success. |
+| §8 Fixtures | `src/data/fixtures` | All listed cases exist as JSON; tests load them. |
+| §9 a11y | CSS + tests | Keyboard, 200% zoom, reduced motion, contrast, announced copy. |
+| §11 checklist | PR template in README | Tick with screenshots, not vibes. |
+
+---
+
+## 7. Inference boundary (brief §7.3)
+
+```ts
+interface InferenceAdapter {
+ capabilities(): Promise;
+ generate(request: GenerationRequest, signal?: AbortSignal): Promise;
+}
+```
+
+- `DisabledAdapter`: capabilities.available = false. `generate` throws
+ `GenerationUnavailable`.
+- `MockAdapter`: capabilities.mock = true. Every response includes
+ `disclaimer: 'Example UI response — not generated by a released OpenVerifiableLLM model'`.
+ If `response.releaseId !== request.releaseId` → reject.
+- Production bundle must not import a path that enables mock by default.
+ Gate with `import.meta.env.VITE_INFERENCE=mock` **and** a visible UI flag.
+
+No Ollama in v1. Connecting Ollama to unverified weights is how we
+accidentally ship the product the brief forbids.
+
+---
+
+## 8. What can be “crazy” (attention) vs what cannot
+
+Mentors (Bruno, Archit, ryoari) punish fake green and template landings.
+They notice **one true interaction** that the rest of AOSSIE does not have.
+
+### Build these — they are distinctive and on-brief
+
+1. **Claim strip (signature)**
+ Every route states what the page may and may not claim, plus snapshot
+ time and source commit. Screenshot this in the PR.
+
+2. **Broken chain rail**
+ Parent → this → children. Missing parent is a physical gap. This is
+ the G09 “publication must not overclaim” idea as UI.
+
+3. **Fail-closed empty docket**
+ A designed screen for “zero checks” and “malformed snapshot.” Most
+ dashboards fake a happy empty state. Ours must look unfinished.
+
+4. **Supersession timeline**
+ FAIL report remains, struck, with an arrow to the later PASS. History
+ is not rewritten. Unique vs OrgExplorer’s live graphs.
+
+5. **Narrow browser checksum (optional M3+)**
+ User drops a *small* published file; Web Crypto SHA-256 compared to
+ the snapshot digest. Label: **checksum of this file in this browser,
+ not training replay, not publisher trust.** Fits “browser checksum
+ must be labelled as that narrow check.”
+
+6. **Fixture / public mode switch that is ugly on purpose**
+ A persistent “FIXTURE” tape on the viewport when not in public mode.
+ Impossible to screenshot by accident as production.
+
+### Do not build — looks busy, costs GSoC trust
+
+- Magic UI / Aceternity / 3D / custom cursor / glass hero
+- RAG “this sentence is from Wikipedia page N” (the txt diagram)
+- Live Ollama chat as the homepage
+- Training/RunPod/cost dashboards
+- Invented CLI copy buttons
+- Accounts, wallets, visitor prompt logs
+
+Cinematic kits stay as **140–220ms opacity/transform** on copy, expand,
+and pipeline stagger. That is the synthesis of the reference txt.
+
+---
+
+## 9. Tests (brief §11)
+
+Run offline. No Wikipedia dump, no GPU.
+
+| File | Asserts |
+| --- | --- |
+| `validate.test.ts` | bad schema → error; empty checks → not success |
+| `status.test.ts` | alias map; `"ok"` is not PASS |
+| `releases.test.ts` | not-released hides download href |
+| `adapter.test.ts` | mock identity mismatch rejected; disabled throws |
+| `navigation.test.ts` | Playwright/Vitest browser: overview → evidence → unreleased releases |
+
+CI later: `frontend.yml` only, on `frontend/**`. Do not edit signing
+or training workflows.
+
+---
+
+## 10. Git rules for this repo
+
+- Branch from **current `main`**: `frontend/evidence-docket`
+- Touch **only** `frontend/`
+- Never commit `keys/`, `.venv`, `tmp_smoke`, `src/` LSTM hacks, `goal_state.json`
+- Do not force-push shared branches
+- Draft PR as soon as `npm run dev` shows overview + claim strip
+- Small commits, one concern each (scaffold / types / pages / tests)
+
+Handoff in the PR body: screenshots mobile+desktop, fixture vs public,
+missing parent, unreleased releases, exact maintainer inputs still needed.
diff --git a/frontend/DESIGN.md b/frontend/DESIGN.md
new file mode 100644
index 00000000..6474ac80
--- /dev/null
+++ b/frontend/DESIGN.md
@@ -0,0 +1,405 @@
+# OpenVerifiableLLM public site — UI plan
+
+For: ryoari / AOSSIE OpenLLM. Owner of `frontend/` only.
+Stack: React, TypeScript, Vite, ordinary CSS. Static. No backend.
+Models: **not released**. Chat is a labelled shell, not a product.
+
+This is the product cut of `FRONTEND_CONTRIBUTOR_BRIEF.md`. The brief is the
+list of things we must not lie about. This file is what a visitor actually sees.
+
+---
+
+## One-line design read
+
+A public **evidence docket** for a research release that does not exist yet:
+calm, typeset, forensic. Not a chatbot landing, not a SaaS dashboard.
+
+If someone closes the tab they should remember the **claim strip** — a persistent
+sentence of what this page is allowed to say — and a **chain with a missing
+link** when a parent is absent.
+
+---
+
+## What the site is for
+
+A visitor should leave able to answer:
+
+1. What is OpenVerifiableLLM trying to prove?
+2. Which public sources and transforms are in play?
+3. What was actually checked, by whom, in what scope?
+4. Where are the original artifacts?
+5. Are the models out? (today: no)
+6. Later: how do I download, verify, and run the *exact* weights?
+
+If the homepage looks like ChatGPT, we failed the assignment and the org.
+
+---
+
+## Visual lock
+
+Do not use Inter, purple glow, glass cards, three feature tiles, or a giant
+green “VERIFIED” badge.
+
+| Token | Hex | Use |
+| --- | --- | --- |
+| Paper | `#F3F0E8` | Page ground (warm, not cream-terracotta UI kit) |
+| Ink | `#1B2320` | Body and headings |
+| Mute | `#5E6A64` | Secondary text |
+| Rule | `#D4D0C4` | Hairlines, table rules |
+| Pass | `#2C6A4E` | Scoped pass only — never a page background |
+| Fail | `#9B3B32` | Scoped fail |
+| Hold | `#A67C2D` | NOT_RUN / pending / not released |
+| Link | `#215A8F` | Evidence and external refs |
+
+**Type**
+
+- Display / titles: `Source Serif 4`
+- UI / body: `Source Sans 3`
+- Digests, commands, IDs: `Source Code Pro`
+
+**Signature element (one place, then quiet)**
+
+A **claim strip** under the header on every route:
+
+```
+This page may claim: data reconstruction (project-operated) PASS
+It may not claim: a released model · full training replay · answer accuracy
+Snapshot 2026-09-19 · source commit abc1234
+```
+
+Evidence pages add a **chain rail** on the left of the detail view: parent →
+this object → children. A missing parent is an empty notch with “link absent”,
+not a skipped row.
+
+Motion: 150–200ms opacity/transform on expand and copy. Honor
+`prefers-reduced-motion`. No looped animation.
+
+---
+
+## Reference kits (`I am giving you these repositories.txt`)
+
+Those six libraries are **interaction textbooks**, not a look to clone:
+
+Motion Primitives · Magic UI · Aceternity UI · React Bits · Cult UI · Animate UI
+
+The same note also says avoid AI slop, random animation, and generic shadcn.
+Taken literally, “cinematic + 3D + cursor-reactive + glass + futuristic AI
+product” **is** that slop, and it fights ryoari’s brief (restrained research
+site, no decorative dashboards).
+
+We steal craft. We do not steal chrome.
+
+| Steal | Where it lands | Refuse |
+| --- | --- | --- |
+| Staggered reveal of the pipeline steps | Overview rail, 40ms stagger, once | Full-page cinematic intro |
+| Hover/press on copy, filter, row | Digests, table rows, buttons | Custom cursor, magnetic buttons |
+| Height/opacity on expand | Evidence JSON, claim details | Accordion everywhere |
+| Route fade (opacity + 8px) | Between the five pages | Shared-element 3D page flips |
+| Skeleton shimmer *only* on evidence fetch | `/evidence` loading | Spinners on static copy |
+| Spatial hierarchy: one column, deep type | Whole site | Bento grids, glass panels |
+| Animated *state* (PASS appearing, FAIL flash) | Result marks | Looping background shaders |
+| Scroll: pipeline pins then releases | Overview only, optional | Scroll hijack, parallax dust |
+
+Implementation rule: `transform` and `opacity` only, 140–220ms, `ease-out`.
+`prefers-reduced-motion` drops travel. No `transition: all`. No particles.
+Do not add Aceternity/Magic UI as dependencies; reimplement the two or three
+patterns we need in CSS.
+
+**The ASCII diagram in that file is a different product.** It is:
+
+```
+user query → LLM → evidence retrieval + claims extraction
+ → verification → provenance graph → verified response
+```
+
+That is a **RAG / claim-checking chatbot**. OpenVerifiableLLM is the opposite
+direction: prove the *model and training*, not each chat sentence against a
+Wikipedia article. The contributor brief forbids RAG and article-attribution.
+Do not build that graph as the homepage. The provenance we show is the
+**training evidence chain** (dump → extract → train → replay → release).
+
+If `/try` ever exists for real, a response may link to *model identity*
+(release root, decoding recipe), never “this token came from page 4412.”
+
+---
+
+## Information architecture
+
+Five routes. Nothing else in v1.
+
+| Route | Purpose |
+| --- | --- |
+| `/` | What this is, current honest status, three next actions |
+| `/evidence` | Filterable docket of public checks and artifacts |
+| `/evidence/:id` | One object: digest, parents, children, original link |
+| `/releases` | Base card + chat card, both gated until real metadata |
+| `/verify` | Five verification *profiles*, not a magic button |
+| `/try` | Inference shell. Public: disabled. Fixture preview: labelled mock |
+
+Nav: Overview · Evidence · Releases · Verify · Try
+Footer: AOSSIE, GitHub, Hugging Face evidence dataset, snapshot time, “this is
+a viewer, not a verifier.”
+
+---
+
+## What must be on each screen
+
+### 1. Overview `/`
+
+**Needed**
+
+- One paragraph in plain language: a small model whose *declared* public
+ inputs, transforms, and training steps can be inspected and replayed.
+- Availability line in Hold color: **No model release yet.**
+- Three actions only: Explore evidence · View releases · How verification works
+- Pipeline as a **vertical sequence** (this *is* a sequence, so order is allowed):
+ Wikipedia dump → extract/exclude ledger → tokenizer/streams → init →
+ Wikipedia training → conversation training → full replay → publication
+- Each step shows one of: PASS / FAIL / NOT_RUN / UNAVAILABLE / UNSUPPORTED
+ plus **scope** (fixture | pilot | production) and **who** (publisher report vs
+ project-operated vs not done)
+- One box: **Provenance is not truth.** Training on Wikipedia does not make a
+ sentence a citation.
+
+**Do not**
+
+- Progress percentage (“G01–G02 = 20% done”)
+- Fake completion date
+- Fake assistant reply in the hero
+- A site-wide green badge
+
+**Craft**
+
+The sequence is the page. No card grid. Type + a left rail of status ticks.
+The claim strip is the hero, not a slogan.
+
+### 2. Evidence explorer `/evidence`
+
+This is the product. Mentors will judge GSoC potential here.
+
+**Needed**
+
+- Table/list as the primary UI (brief requires this; diagram is optional extra)
+- Filters: phase, kind, result, scope (fixture / pilot / production)
+- Columns: title · phase · scope · result · performed by · attested by · when · source
+- Click row → detail
+- Empty, loading, fetch-fail, and “zero checks” states that **cannot** look like success
+
+**Groups** (filters, not eight separate apps)
+
+1. Wikipedia source + acquisition
+2. OASST / conversation source
+3. Extraction + inclusion/exclusion
+4. Tokenizer + streams
+5. Init + frozen config
+6. Training boundaries / checkpoints
+7. Reconstruction + replay reports
+8. Release artifacts + clean download
+
+**Craft**
+
+- Result is a word plus a mark, never color alone
+- Fixture rows have a persistent “FIXTURE” tag so they cannot be skimmed as production
+- Superseded reports stay listed, struck through, with a link to the successor
+- Do not fetch dumps, shards, or checkpoints. Show **size + download link**
+
+### 3. Evidence detail `/evidence/:id`
+
+**Needed**
+
+- Title, kind, scope, result, explanation
+- Full digest with copy (truncate visually, copy full)
+- `performedBy` / `attestedBy` / `locallyRecomputed` as three separate fields
+ (`locallyRecomputed` is always false on this static site)
+- Parent IDs and child IDs; missing parent = broken chain, not omitted
+- Link to original (GitHub blob, HF file, commit)
+- Expandable escaped JSON, bounded (e.g. 64 KB). Not a second verifier.
+
+**Craft**
+
+Chain rail is here. If parent is missing, the rail has a gap you can see without
+reading the table. That screenshot is the one that belongs in the PR.
+
+### 4. Releases `/releases`
+
+Two panels, stacked on mobile, side by side on desktop: **Base** and **Chat**.
+
+**Until maintainer metadata exists (today)**
+
+- Availability: **Not released yet**
+- Download and Generate controls present but **disabled**, with a sentence next
+ to them: why, and what will unlock them
+- Chat panel states it is a derivative of base, so it cannot pretend to be
+ “Wikipedia-only”
+
+**When a real release exists**
+
+- Role, repo, immutable revision, release root
+- Param count, file sizes, loader — only from metadata
+- License/attribution from that release
+- Chat → exact base parent
+- Verified *scope* (which profiles passed), links into evidence
+- Download instructions + limitations
+
+**Do not** invent HF repo names, leaderboard scores, or placeholder `.safetensors`.
+
+### 5. Verify `/verify`
+
+Five profiles as five blocks. Each block:
+
+- Name
+- What a PASS is allowed to mean (one sentence from the brief table)
+- What it does **not** mean
+- Status of *this project* for that profile, from the snapshot
+- Command area: **Instructions pending release** until ryoari gives a tested
+ command. No copy button on a guessed CLI.
+
+**Needed warning:** complete replay is not a free browser check. If we ever show
+cost/time, label estimate vs actual bill.
+
+**Craft**
+
+This page teaches claim hygiene. It should feel like a legend for the rest of
+the site, not a marketing FAQ.
+
+### 6. Try `/try`
+
+Public build:
+
+- Model picker visible, both options disabled
+- Composer visible, send disabled
+- Banner: **Generation is unavailable until a verified model release is connected**
+- No silent fallback to GPT / Gemini / “any Ollama model”
+
+Fixture / preview build (`?fixture=1` or a documented env flag):
+
+- Mock adapter only
+- Every response carries a visible, sticky label:
+ **Example UI response — not generated by a released OpenVerifiableLLM model**
+- Base = completion box; Chat = role-aware thread
+- States: empty, loading, error, retry, identity mismatch (adapter returns a
+ different release than selected → reject)
+- Copy response. Receipt download only when a real backend exists
+- No prompt logging, no analytics, no localStorage of visitor chats in v1
+
+This is how we honor “he asked for a chat interface” without shipping a lie.
+
+---
+
+## Status vocabulary (render exactly)
+
+Verification result ≠ workflow ≠ browser state.
+
+| Result | Meaning on screen |
+| --- | --- |
+| PASS | This named check passed, inside its reported scope |
+| FAIL | It ran and did not meet requirements |
+| NOT_RUN | Not performed |
+| UNAVAILABLE | Evidence or resources missing |
+| UNSUPPORTED | Cannot be done on this environment/format |
+
+Never map arbitrary strings like `ok` / `complete` / `true` to PASS.
+
+Always show, when a check is displayed:
+
+- profile · artifact · scope · who performed · who attested · locally recomputed?
+
+Independence is not inferred from “two machines” or “a signature.”
+
+---
+
+## Data: viewer, not verifier
+
+- Versioned presentation snapshot in `frontend/public/data/`
+- Adapter maps maintainer JSON → view types. Keep source URLs/revisions.
+- Two modes: `fixture` and `public-snapshot`. Failed fetch **must not** fall
+ back to a successful mock.
+- `goal_state.json` is navigation for *him*, not a public API. Do not scrape it
+ from the client. He supplies an approved snapshot when he has one.
+
+Minimum view types (implement these, not a fantasy backend):
+
+```ts
+type CheckResult = 'PASS' | 'FAIL' | 'NOT_RUN' | 'UNAVAILABLE' | 'UNSUPPORTED';
+type EvidenceScope = 'fixture' | 'pilot' | 'production';
+type ReleaseAvailability = 'not-released' | 'available' | 'withdrawn';
+```
+
+v1 fixtures (enough, not twelve):
+
+1. Public-shaped snapshot: G01/G02-like PASS, models not-released, replay NOT_RUN
+2. Fixture-only success that is visually unskippable as fixture
+3. FAIL hash + missing parent
+4. Superseded FAIL then later PASS
+5. Empty check list (must not look green)
+6. Fetch error / stale snapshot
+
+---
+
+## What we will not build (even though we have “freedom”)
+
+These would look busy and would violate the brief or AOSSIE’s less-cloud stance.
+
+- Accounts, wallets, comments, stars
+- Training / RunPod / cost control panels
+- Browser-side full replay or auto-download of weights
+- RAG, “this sentence came from article X”
+- Live Ollama against unverified local models as the default path
+- Invented `ovllm` CLI on a copy button
+- Polling dashboards, graphs of GPU utilization
+- A second design system or a UI kit theme
+
+Freedom is in **how clearly the docket reads**, not in extra product surface.
+
+---
+
+## Execution bar (this is the GSoC-looking part)
+
+Mentors remember restraint plus finish, not a 20-page mock.
+
+- Keyboard: skip link, focus ring, no trap in dialogs
+- Status not color-only
+- 200% zoom, mobile table that becomes stacked definition lists
+- Dates with timezone; sizes with units
+- Copy digest announces “copied” to the button, not only visually
+- External links marked external; hashes never re-serialized as “the same bytes”
+- `npm run dev|typecheck|test|build` documented and green
+- GitHub Pages base path considered (`basename` / Vite `base`)
+- Screenshots in the PR: desktop + mobile, including **not released** and
+ **missing parent**
+
+---
+
+## Build order
+
+| Cut | Ship | Why |
+| --- | --- | --- |
+| M1 | Routes, tokens, claim strip, two fixtures, overview + evidence list | Proves we understood the brief |
+| M2 | Detail chain rail, releases gated, verify legend | The memorable UI |
+| M3 | One maintainer-approved public snapshot adapter | Honesty with real links |
+| M4 | `/try` disabled + labelled mock | Honors the Discord ask without a fake model |
+| M5 | a11y pass, tests, static build, README handoff | Mergeable |
+
+Do not wait for Wikipedia training to finish. Do not wait to implement all
+fixtures before opening a draft PR.
+
+---
+
+## Questions for ryoari (only these)
+
+1. Confirm v1 is the **evidence site**, with `/try` gated, not a chat app.
+2. When can we have one **approved public snapshot** (commit + files), vs fixtures only?
+3. Hosting: GitHub Pages under AOSSIE, or wait?
+4. Do not send CLI copy until he pastes a command he has actually run.
+
+---
+
+## Why this is the right amount of work
+
+The org does not need another Ollama skin. It needs a public page that can sit
+next to a GSoC demo and not overclaim. That is rare, it matches G09 in
+`PROJECT_GOAL.md`, and it is entirely inside `frontend/`.
+
+Done looks like: a stranger opens `/`, reads that models are unreleased, opens
+evidence, copies a digest, follows a broken parent, and understands the
+difference between “we hashed the dump” and “we replayed every step.”
diff --git a/frontend/README.md b/frontend/README.md
new file mode 100644
index 00000000..feb1ac08
--- /dev/null
+++ b/frontend/README.md
@@ -0,0 +1,46 @@
+# OpenVerifiableLLM frontend
+
+Static evidence docket. It does **not** train, sign, or talk to a live model.
+
+Requires Node 20+.
+
+```sh
+cd frontend
+npm install
+npm run dev
+npm run test
+npm run typecheck
+npm run build
+```
+
+Open http://localhost:5173 — HashRouter, so routes look like `/#/evidence`.
+
+## Snapshot packs
+
+The app never scrapes `goal_state.json`. It loads a frozen JSON pack:
+
+| `?snapshot=` | What it is |
+| --- | --- |
+| *(default)* `public` | Honest project-shaped snapshot: G01/G02-like PASS, models **not released**, full replay NOT_RUN |
+| `missing-parent` | Fixture: child names a parent that is not in the docket |
+| `superseded` | Fixture: FAIL remains listed after a later PASS |
+| `empty` | Fixture: zero checks — shown as an error, not success |
+
+Example: `/#/?snapshot=missing-parent`
+
+## Claims this UI will not make
+
+- Models are available
+- Full training replay passed
+- `locallyRecomputed` is true (always false here)
+- Copyable CLI (withheld until a tested command exists)
+- Chat answers come from a released OpenVerifiableLLM model
+
+## Maintainer still needs to provide
+
+- Approved public snapshot + git revision
+- Tested verify commands
+- GitHub Pages `base` path / URL
+- Inference backend after a real release
+
+Do not edit `src/` (pipeline), keys, or `project/` from this app.
diff --git a/frontend/index.html b/frontend/index.html
new file mode 100644
index 00000000..2fd2e987
--- /dev/null
+++ b/frontend/index.html
@@ -0,0 +1,22 @@
+
+
+
+
+
+ OpenVerifiableLLM — evidence docket
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
new file mode 100644
index 00000000..908b2845
--- /dev/null
+++ b/frontend/package-lock.json
@@ -0,0 +1,3070 @@
+{
+ "name": "openverifiablellm-frontend",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "openverifiablellm-frontend",
+ "version": "0.1.0",
+ "dependencies": {
+ "react": "^19.1.1",
+ "react-dom": "^19.1.1",
+ "react-router-dom": "^7.8.2"
+ },
+ "devDependencies": {
+ "@testing-library/react": "^16.3.0",
+ "@testing-library/user-event": "^14.6.1",
+ "@types/react": "^19.1.12",
+ "@types/react-dom": "^19.1.9",
+ "@vitejs/plugin-react": "^5.0.2",
+ "jsdom": "^26.1.0",
+ "typescript": "~5.9.2",
+ "vite": "^7.1.5",
+ "vitest": "^3.2.4"
+ }
+ },
+ "node_modules/@asamuzakjp/css-color": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz",
+ "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/css-calc": "^2.1.3",
+ "@csstools/css-color-parser": "^3.0.9",
+ "@csstools/css-parser-algorithms": "^3.0.4",
+ "@csstools/css-tokenizer": "^3.0.3",
+ "lru-cache": "^10.4.3"
+ }
+ },
+ "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
+ "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
+ "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
+ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-compilation-targets": "^7.29.7",
+ "@babel/helper-module-transforms": "^7.29.7",
+ "@babel/helpers": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz",
+ "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.8",
+ "@babel/types": "^7.29.8",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
+ "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.29.7",
+ "@babel/helper-validator-option": "^7.29.7",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
+ "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
+ "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
+ "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "@babel/traverse": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
+ "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
+ "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
+ "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz",
+ "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.9",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz",
+ "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.8"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz",
+ "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz",
+ "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz",
+ "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
+ "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz",
+ "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.8",
+ "@babel/helper-globals": "^7.29.7",
+ "@babel/parser": "^7.29.8",
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.8",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz",
+ "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@csstools/color-helpers": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
+ "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT-0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@csstools/css-calc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz",
+ "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-color-parser": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz",
+ "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/color-helpers": "^5.1.0",
+ "@csstools/css-calc": "^2.1.4"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-parser-algorithms": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
+ "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-tokenizer": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
+ "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz",
+ "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz",
+ "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz",
+ "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz",
+ "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz",
+ "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz",
+ "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz",
+ "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz",
+ "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz",
+ "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz",
+ "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz",
+ "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz",
+ "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz",
+ "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz",
+ "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz",
+ "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz",
+ "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz",
+ "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz",
+ "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz",
+ "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz",
+ "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz",
+ "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz",
+ "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz",
+ "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz",
+ "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz",
+ "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz",
+ "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz",
+ "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@napi-rs/lzma-linux-x64-gnu": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz",
+ "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^22.20 || ^24.12 || >=25"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-rc.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz",
+ "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.4.tgz",
+ "integrity": "sha512-I+BSHzTAhKN2n7ZwGZsegGcZjDpLqFOMAtJz/u6uFGe0pUFbq56dEHjqJV/ZUdRJtNXNxA+hREUatZBvMR3Oiw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.4.tgz",
+ "integrity": "sha512-pu3BdjS2LtEzRu2elmGzS3fIeWSZy4BMDIaLNwjorO76+k2d0LMluijhsDx3KQyQBQ/lLUZCQA9/s6csvUfuhw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.4.tgz",
+ "integrity": "sha512-xfSrj9MHnWK9GaSqT9U0ImHtH/N8WZlHLx4cZHiuLcqs640hvZ3hLPd5UR2AZS57FaE8HrRUSpltbZdWRxHiDA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.4.tgz",
+ "integrity": "sha512-bqU99PLJb/dqb3S0GIMdeuyAEETSUgZBoqXYd3Sd+WCsV+MmPhnN6JrotWyir31+QgH7EvvE5/mwGJlEoci8Fw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.4.tgz",
+ "integrity": "sha512-JinsFZ5G40oXQb+sUuiA5x689vhr6dDYK0H0NL+rwKdL6CqnmYN8PE4ZwfRSoIjrCxqTQG/SLfTtSvHeGxoVlw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.4.tgz",
+ "integrity": "sha512-GAdA4UxpiNm27cLHr2GqXBpAD0x9FqwYBY7/YSP0Ss0/PNi4k8gbviqpIpYbVSRBaS2ZcegXEzgTQMbRNCwxCw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.4.tgz",
+ "integrity": "sha512-qDd6NoA1znaLjp4jR5U/KWCdLAKDJNB8W9ChbbDaKbo0xA+Atln5HK6LFCZ4oJQpemtRZA288DCirFRjrspptw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.4.tgz",
+ "integrity": "sha512-WtB5Tz5KTNINb8ZA+8sQ7bmjuS1JrRT7YverYIhUGdWWDlpzVWmIwuZE+jidkEXUn1l0zrEkaIMa8dHF3NGcsA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.4.tgz",
+ "integrity": "sha512-VcQ3L1tjnkKzWjryAVaFhHEWcqOfICX9uxVVoDzm2t0DpgKRHd2zOpVrJc0xsWeBZcBFyYROCIBdyR/fS174pg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.4.tgz",
+ "integrity": "sha512-6+ZQX6P5s0cMDN2Ypb8Lbm2+/sZYmZjdaYny992ujUU9UKi/4CWoJWsl1pNvjWJHNHGK51m+jKGLlh1ylb2ifQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.4.tgz",
+ "integrity": "sha512-D72ZnvkFkBXOfzMMQLcwfPLyGkKb7HZ9/mf97B7v6/P5Lbv4oFOtSY/uHbS8lH6uKUOxoKiuokdb50XZSzzbJw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.4.tgz",
+ "integrity": "sha512-piU6BxeqA3O9KSu3kRCIQQtNqFFaTu21SEV4FwaRZowpnj3bLaWPZHw+xFqCs0XlJ+aOH3PTRWGoglH+mKA/OA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.4.tgz",
+ "integrity": "sha512-/5PGpHwqt2EEEOUs1XwzubE/ucr0dWDQ+to3zqi4Ds7EWpwtQ79wXc4JBoxqj/OwpawTsKWzJxHfSuBOq3DrWA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.4.tgz",
+ "integrity": "sha512-cX3beZDLWt7G2oJF+nhChiT+qtaihs+S2xi7ziGmVB+2pwPng6D0Ed0HmElQOgv2UsUmSJJLGwpBao/3TDx3VA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.4.tgz",
+ "integrity": "sha512-1uz2mGWHyptR7DgHHrlbdRAjXK7v7elGZ9lMja910/RP+ZYbX6xAmCiU9UZSX4hqmgtHMv6lr5l3kq1HIOpcag==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.4.tgz",
+ "integrity": "sha512-nLS8topojxyz7SRpKR2IODRpQ0XPZ+xaOXvT3+hqK/Uy8Lo5HFgkkIBiIrCu5tL5YqzTvgovGw55PwpahTAGig==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.4.tgz",
+ "integrity": "sha512-gs7DRKotr3l3q+jGPQBjH0ng1FjlEDm5ueQrkw5JtQvtLyEIcLASqAEaor56BhkKRzk+IcQzrcanBdb/bBQn8g==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.4.tgz",
+ "integrity": "sha512-791ET7W17NnScOZM7h4dX5hYspxE28htPFsb1awY/NRR8+PRNkS53e475rDdxXXDrP+kwnCcNWg9CX5ztn/Aqw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.4.tgz",
+ "integrity": "sha512-iwZQRcmj7g88g3tzefIrQY7qvmuA/cfYwhrDtTBhsmukO4U2huVO5W+86XacUMRvdSFVAc6kZUZy21JaRwiB9w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.4.tgz",
+ "integrity": "sha512-dVHFp9gRWrdTpnqQuGfCwd7hOQDatK1VCP2iWhLY/cGrOQs/ucFzJ6A5SRqbXX12ZDI8EUuejSM5kwg+ja7Png==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.4.tgz",
+ "integrity": "sha512-t3NlauOW6gxZVVFcBEnO62Cb4wbyDFL416gTg1uFI/2tgqYQlf69FbSE115Ajre9I+c26Lk4mcmdFUsS/DGifQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.4.tgz",
+ "integrity": "sha512-xWuIaSye5FWZF8+UYtVEcHtRJDN5kN9Kfgxx3Kq8XIov9KSKbc1fiqQCm90SKrgQbUXZelbnUhnlUJmfSE7P9A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.4.tgz",
+ "integrity": "sha512-9ALJJUOg/ZflMJepVo2PlgsGxSaxN7SQ4Z8GoZfVlarWr6r3rkHUNsd/zAio7p4YMtChSMXPionxej4Hkf6CXQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.4.tgz",
+ "integrity": "sha512-blj9z5qx/Pv4WU0W1NMFDB97e0JH5ed+aZGywW8WCvp/NhWX/4PFAq5uu6Q0AebNn+Vo6KzUYDT++JzTT5ojlQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.4.tgz",
+ "integrity": "sha512-Erx822VRBwLa124shbj+wNXe//BOgMEctDV0m1aqTQdNO1S69DgNUCFKC1RCeZfixs1J31l6igk1ziyXErbigQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@testing-library/dom": {
+ "version": "10.4.2",
+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.2.tgz",
+ "integrity": "sha512-yzr2S9HyAIdhz2/6qHgbs665Q7PKVcDF05vsOlHPxG1mo36gKVesdYVeDLnXgfjJ03CrKRk08knc6+E/9m8v2Q==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/runtime": "^7.12.5",
+ "@types/aria-query": "^5.0.1",
+ "aria-query": "5.3.0",
+ "dom-accessibility-api": "^0.5.9",
+ "lz-string": "^1.5.0",
+ "picocolors": "1.1.1",
+ "pretty-format": "^27.0.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@testing-library/react": {
+ "version": "16.3.3",
+ "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.3.tgz",
+ "integrity": "sha512-Uo193NgQbPMz6lrrhtRQQFcMC6Re/ELLFbbuVL30WDlZxlpZf9/lMHTAVxPRLw1q1iu9OJmR1c2BLiENRstdBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@testing-library/dom": "^10.0.0",
+ "@types/react": "^18.0.0 || ^19.0.0",
+ "@types/react-dom": "^18.0.0 || ^19.0.0",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@testing-library/user-event": {
+ "version": "14.6.7",
+ "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.7.tgz",
+ "integrity": "sha512-MPCpX8bxe8zS+JmmTwLp8jd0dy1rAm60Te/SL8JrQM3qvQJcBOs1d7IefJMyZzqM3EWBrDn/LWDt1BCGu4ASfg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ },
+ "peerDependencies": {
+ "@testing-library/dom": ">=7.21.4"
+ }
+ },
+ "node_modules/@types/aria-query": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
+ "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/chai": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
+ "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/deep-eql": "*",
+ "assertion-error": "^2.0.1"
+ }
+ },
+ "node_modules/@types/deep-eql": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+ "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "19.3.0",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz",
+ "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.3.0",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz",
+ "integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.3.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz",
+ "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.29.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-rc.3",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.18.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/@vitest/expect": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.7.tgz",
+ "integrity": "sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "3.2.7",
+ "@vitest/utils": "3.2.7",
+ "chai": "^5.2.0",
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/mocker": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.7.tgz",
+ "integrity": "sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "3.2.7",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.17"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.7.tgz",
+ "integrity": "sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.7.tgz",
+ "integrity": "sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "3.2.7",
+ "pathe": "^2.0.3",
+ "strip-literal": "^3.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.7.tgz",
+ "integrity": "sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "3.2.7",
+ "magic-string": "^0.30.17",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.7.tgz",
+ "integrity": "sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyspy": "^4.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.7.tgz",
+ "integrity": "sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "3.2.7",
+ "loupe": "^3.1.4",
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/aria-query": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
+ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "dequal": "^2.0.3"
+ }
+ },
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.11.25",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.25.tgz",
+ "integrity": "sha512-gMmEShwwq7FJqMwvfRwvCl00v4kN+KOfJqXn+f4nrufak5gNHJOksd/60Dvjuz7sI8Y5WiSFBa8FEYr+zoyqCw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.29.0",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.0.tgz",
+ "integrity": "sha512-3GSvyjvDI4Dur1Meg2BekJquu5uF+9R9a1+5M1Mde192eZoXbeXjzgOsgqPS2V8D5wrrip0gR5Hf/GhWQ9ZzaA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.11.23",
+ "caniuse-lite": "^1.0.30001810",
+ "electron-to-chromium": "^1.5.427",
+ "node-releases": "^2.0.55",
+ "update-browserslist-db": "^1.3.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/cac": {
+ "version": "6.7.14",
+ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+ "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001810",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz",
+ "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chai": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz",
+ "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "assertion-error": "^2.0.1",
+ "check-error": "^2.1.1",
+ "deep-eql": "^5.0.1",
+ "loupe": "^3.1.0",
+ "pathval": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/check-error": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz",
+ "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
+ "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/cssstyle": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz",
+ "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@asamuzakjp/css-color": "^3.2.0",
+ "rrweb-cssom": "^0.8.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/data-urls": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
+ "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^14.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decimal.js": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
+ "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/deep-eql": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
+ "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/dom-accessibility-api": {
+ "version": "0.5.16",
+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
+ "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.433",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.433.tgz",
+ "integrity": "sha512-5lCAbyZBjtmUt/RAGHRqrL2q0oEFRThDAsZHHDn9XHa89Qw7gMYOeSicBTy+AHfvo0r6vwsZvqNJTQIQy1BLzA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/entities": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
+ "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/es-module-lexer": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
+ "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/esbuild": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz",
+ "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.28.2",
+ "@esbuild/android-arm": "0.28.2",
+ "@esbuild/android-arm64": "0.28.2",
+ "@esbuild/android-x64": "0.28.2",
+ "@esbuild/darwin-arm64": "0.28.2",
+ "@esbuild/darwin-x64": "0.28.2",
+ "@esbuild/freebsd-arm64": "0.28.2",
+ "@esbuild/freebsd-x64": "0.28.2",
+ "@esbuild/linux-arm": "0.28.2",
+ "@esbuild/linux-arm64": "0.28.2",
+ "@esbuild/linux-ia32": "0.28.2",
+ "@esbuild/linux-loong64": "0.28.2",
+ "@esbuild/linux-mips64el": "0.28.2",
+ "@esbuild/linux-ppc64": "0.28.2",
+ "@esbuild/linux-riscv64": "0.28.2",
+ "@esbuild/linux-s390x": "0.28.2",
+ "@esbuild/linux-x64": "0.28.2",
+ "@esbuild/netbsd-arm64": "0.28.2",
+ "@esbuild/netbsd-x64": "0.28.2",
+ "@esbuild/openbsd-arm64": "0.28.2",
+ "@esbuild/openbsd-x64": "0.28.2",
+ "@esbuild/openharmony-arm64": "0.28.2",
+ "@esbuild/sunos-x64": "0.28.2",
+ "@esbuild/win32-arm64": "0.28.2",
+ "@esbuild/win32-ia32": "0.28.2",
+ "@esbuild/win32-x64": "0.28.2"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/expect-type": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz",
+ "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/html-encoding-sniffer": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
+ "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-encoding": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/http-proxy-agent": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-potential-custom-element-name": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
+ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/jsdom": {
+ "version": "26.1.0",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz",
+ "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssstyle": "^4.2.1",
+ "data-urls": "^5.0.0",
+ "decimal.js": "^10.5.0",
+ "html-encoding-sniffer": "^4.0.0",
+ "http-proxy-agent": "^7.0.2",
+ "https-proxy-agent": "^7.0.6",
+ "is-potential-custom-element-name": "^1.0.1",
+ "nwsapi": "^2.2.16",
+ "parse5": "^7.2.1",
+ "rrweb-cssom": "^0.8.0",
+ "saxes": "^6.0.0",
+ "symbol-tree": "^3.2.4",
+ "tough-cookie": "^5.1.1",
+ "w3c-xmlserializer": "^5.0.0",
+ "webidl-conversions": "^7.0.0",
+ "whatwg-encoding": "^3.1.1",
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^14.1.1",
+ "ws": "^8.18.0",
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "canvas": "^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "canvas": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/loupe": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
+ "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/lz-string": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
+ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "lz-string": "bin/bin.js"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.19",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz",
+ "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.56",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.56.tgz",
+ "integrity": "sha512-x0InOIyzgdk+eyaWaRJFH5snEtiImgBgblZ2CyPrLmqqcuMQkEvcDPHbzqbD8eDsSeJbVOjn+crzyzHaM4D+/A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/nwsapi": {
+ "version": "2.2.28",
+ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.28.tgz",
+ "integrity": "sha512-IlVB7OS7qrOsVYlpnFIkETjMwT9jwvmocJmmM+GZU/PAB3uGi9Ezd7vcWhWBUnSc0ya4ppmQITOyP1ez9gg8cg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/parse5": {
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
+ "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "entities": "^6.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pathval": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz",
+ "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.16"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.28",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz",
+ "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.18",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/pretty-format": {
+ "version": "27.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
+ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^17.0.1"
+ },
+ "engines": {
+ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.3.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz",
+ "integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.3.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.0.tgz",
+ "integrity": "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.28.0"
+ },
+ "peerDependencies": {
+ "react": "^19.3.0"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/react-refresh": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
+ "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-router": {
+ "version": "7.18.4",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.4.tgz",
+ "integrity": "sha512-PUPQcMhMGRAslLcvtlPz/kmzBEWPhLdgLFrL7pLNepBL6dX0lWj4WD2cUYVgYCuT3jxvghYFg81cDTj44DhetQ==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "7.18.4",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.4.tgz",
+ "integrity": "sha512-yrfmJHIpDG7taCpqKjT1G5B6q3O2K+RN8/fgNf0lTjCwiPbQ0ei6vXX9ZjQR+7ld8Tr7Z5xmyMnZ8YJrphWQUw==",
+ "license": "MIT",
+ "dependencies": {
+ "react-router": "7.18.4"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.63.4",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.4.tgz",
+ "integrity": "sha512-4U0liVayNIoLp3GFl1FcI8561WepLnZ1rqfraGh7S9B3Ur5F9S283y8Futii7RUU2C/97tOBmBy7nYvhoiOpbQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.9"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@napi-rs/lzma-linux-x64-gnu": "1.5.1",
+ "@rollup/rollup-android-arm-eabi": "4.63.4",
+ "@rollup/rollup-android-arm64": "4.63.4",
+ "@rollup/rollup-darwin-arm64": "4.63.4",
+ "@rollup/rollup-darwin-x64": "4.63.4",
+ "@rollup/rollup-freebsd-arm64": "4.63.4",
+ "@rollup/rollup-freebsd-x64": "4.63.4",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.63.4",
+ "@rollup/rollup-linux-arm-musleabihf": "4.63.4",
+ "@rollup/rollup-linux-arm64-gnu": "4.63.4",
+ "@rollup/rollup-linux-arm64-musl": "4.63.4",
+ "@rollup/rollup-linux-loong64-gnu": "4.63.4",
+ "@rollup/rollup-linux-loong64-musl": "4.63.4",
+ "@rollup/rollup-linux-ppc64-gnu": "4.63.4",
+ "@rollup/rollup-linux-ppc64-musl": "4.63.4",
+ "@rollup/rollup-linux-riscv64-gnu": "4.63.4",
+ "@rollup/rollup-linux-riscv64-musl": "4.63.4",
+ "@rollup/rollup-linux-s390x-gnu": "4.63.4",
+ "@rollup/rollup-linux-x64-gnu": "4.63.4",
+ "@rollup/rollup-linux-x64-musl": "4.63.4",
+ "@rollup/rollup-openbsd-x64": "4.63.4",
+ "@rollup/rollup-openharmony-arm64": "4.63.4",
+ "@rollup/rollup-win32-arm64-msvc": "4.63.4",
+ "@rollup/rollup-win32-ia32-msvc": "4.63.4",
+ "@rollup/rollup-win32-x64-gnu": "4.63.4",
+ "@rollup/rollup-win32-x64-msvc": "4.63.4",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/rrweb-cssom": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
+ "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/saxes": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+ "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "xmlchars": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=v12.22.7"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz",
+ "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
+ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
+ "license": "MIT"
+ },
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/std-env": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
+ "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/strip-literal": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz",
+ "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^9.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/strip-literal/node_modules/js-tokens": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
+ "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/symbol-tree": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyexec": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
+ "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinypool": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz",
+ "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ }
+ },
+ "node_modules/tinyrainbow": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz",
+ "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tinyspy": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.6.tgz",
+ "integrity": "sha512-u8KszXvGfU68hVcZpRHKG28T0krMuv2G5nDhiHaMLen/gIuFEgIJhaJuO69qjnXg5paSrbPMFfx3brNuN8eVSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tldts": {
+ "version": "6.1.86",
+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
+ "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tldts-core": "^6.1.86"
+ },
+ "bin": {
+ "tldts": "bin/cli.js"
+ }
+ },
+ "node_modules/tldts-core": {
+ "version": "6.1.86",
+ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz",
+ "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tough-cookie": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz",
+ "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "tldts": "^6.1.32"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
+ "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz",
+ "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "7.3.6",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz",
+ "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-node": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz",
+ "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cac": "^6.7.14",
+ "debug": "^4.4.1",
+ "es-module-lexer": "^1.7.0",
+ "pathe": "^2.0.3",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
+ },
+ "bin": {
+ "vite-node": "vite-node.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/vitest": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.7.tgz",
+ "integrity": "sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/chai": "^5.2.2",
+ "@vitest/expect": "3.2.7",
+ "@vitest/mocker": "3.2.7",
+ "@vitest/pretty-format": "^3.2.7",
+ "@vitest/runner": "3.2.7",
+ "@vitest/snapshot": "3.2.7",
+ "@vitest/spy": "3.2.7",
+ "@vitest/utils": "3.2.7",
+ "chai": "^5.2.0",
+ "debug": "^4.4.1",
+ "expect-type": "^1.2.1",
+ "magic-string": "^0.30.17",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.2",
+ "std-env": "^3.9.0",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^0.3.2",
+ "tinyglobby": "^0.2.14",
+ "tinypool": "^1.1.1",
+ "tinyrainbow": "^2.0.0",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0",
+ "vite-node": "3.2.4",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@types/debug": "^4.1.12",
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "@vitest/browser": "3.2.7",
+ "@vitest/ui": "3.2.7",
+ "happy-dom": "*",
+ "jsdom": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@types/debug": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/w3c-xmlserializer": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
+ "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/whatwg-encoding": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
+ "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
+ "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "iconv-lite": "0.6.3"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/whatwg-mimetype": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
+ "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/whatwg-url": {
+ "version": "14.2.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
+ "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tr46": "^5.1.0",
+ "webidl-conversions": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ws": {
+ "version": "8.21.3",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz",
+ "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/xml-name-validator": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
+ "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/xmlchars": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ }
+ }
+}
diff --git a/frontend/package.json b/frontend/package.json
new file mode 100644
index 00000000..b27bdde1
--- /dev/null
+++ b/frontend/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "openverifiablellm-frontend",
+ "private": true,
+ "version": "0.1.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "preview": "vite preview",
+ "typecheck": "tsc -b --noEmit",
+ "test": "vitest run"
+ },
+ "dependencies": {
+ "react": "^19.1.1",
+ "react-dom": "^19.1.1",
+ "react-router-dom": "^7.8.2"
+ },
+ "devDependencies": {
+ "@testing-library/react": "^16.3.0",
+ "@testing-library/user-event": "^14.6.1",
+ "@types/react": "^19.1.12",
+ "@types/react-dom": "^19.1.9",
+ "@vitejs/plugin-react": "^5.0.2",
+ "jsdom": "^26.1.0",
+ "typescript": "~5.9.2",
+ "vite": "^7.1.5",
+ "vitest": "^3.2.4"
+ }
+}
diff --git a/frontend/public/data/README.md b/frontend/public/data/README.md
new file mode 100644
index 00000000..91423486
--- /dev/null
+++ b/frontend/public/data/README.md
@@ -0,0 +1,2 @@
+Display snapshots live in `src/data/fixtures/`. This folder is reserved for a
+maintainer-approved public snapshot file later. Do not dump `project/` here.
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
new file mode 100644
index 00000000..7117944f
--- /dev/null
+++ b/frontend/src/App.tsx
@@ -0,0 +1,28 @@
+import { HashRouter, Route, Routes } from 'react-router-dom';
+import { AppShell } from './layout/AppShell';
+import { EvidenceDetailPage } from './pages/EvidenceDetailPage';
+import { EvidenceListPage } from './pages/EvidenceListPage';
+import { OverviewPage } from './pages/OverviewPage';
+import { ReleasesPage } from './pages/ReleasesPage';
+import { TryPage } from './pages/TryPage';
+import { VerifyPage } from './pages/VerifyPage';
+import { SnapshotProvider } from './snapshot';
+
+export default function App() {
+ return (
+
+
+
+ }>
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
+
+ );
+}
diff --git a/frontend/src/content/copy.ts b/frontend/src/content/copy.ts
new file mode 100644
index 00000000..da492e08
--- /dev/null
+++ b/frontend/src/content/copy.ts
@@ -0,0 +1,164 @@
+import type { CheckResult, EvidenceScope } from '../data/contracts';
+
+export const SCOPE_LABEL: Record = {
+ fixture: 'test data',
+ pilot: 'practice run',
+ production: 'real run',
+};
+
+export const PHASE_LABEL: Record = {
+ source: 'Data',
+ prepare: 'Preparation',
+ train: 'Training',
+ replay: 'Replay',
+ release: 'Release',
+ other: 'Other',
+};
+
+export type JourneyKind = 'evidence' | 'verification' | 'inference';
+
+export interface JourneyStageDef {
+ id: string;
+ label: string;
+ what: string;
+ why: string;
+ pipelineIds: string[];
+ checkProfile: string | null;
+ kind: JourneyKind;
+}
+
+export const JOURNEY: JourneyStageDef[] = [
+ {
+ id: 'data',
+ label: 'Data',
+ what: 'Name the public Wikipedia files used as input.',
+ why: 'Later steps cannot be checked if the starting files are unnamed or private. Conversation data is planned for the chat model, not shown as done here.',
+ pipelineIds: ['wiki-source', 'wiki-acquire'],
+ checkProfile: null,
+ kind: 'evidence',
+ },
+ {
+ id: 'preparation',
+ label: 'Preparation',
+ what: 'Turn those files into the training text and tokens.',
+ why: 'Cleaning and tokenizing must be specified, or training cannot be reproduced.',
+ pipelineIds: ['extract', 'tokenizer'],
+ checkProfile: null,
+ kind: 'evidence',
+ },
+ {
+ id: 'training',
+ label: 'Training',
+ what: 'Train from scratch on that data; save checkpoints.',
+ why: 'Released weights should be the output of this run, not an unexplained extra process.',
+ pipelineIds: ['init', 'wiki-train', 'chat-train'],
+ checkProfile: null,
+ kind: 'evidence',
+ },
+ {
+ id: 'replay',
+ label: 'Replay',
+ what: 'Rebuild the data and run the same training again.',
+ why: 'A checksum or signature does not prove the training description is true. This is verification, not the project’s file list.',
+ pipelineIds: ['replay'],
+ checkProfile: 'full-replay',
+ kind: 'verification',
+ },
+ {
+ id: 'release',
+ label: 'Release',
+ what: 'Publish the model files with checksums.',
+ why: 'Without a public folder and version, there are no weights for others to download.',
+ pipelineIds: ['publish'],
+ checkProfile: null,
+ kind: 'evidence',
+ },
+ {
+ id: 'inference',
+ label: 'Inference',
+ what: 'Reproduce a saved example from released files.',
+ why: 'Generating text is not the same as proving how the model was trained. Live chat is not this check.',
+ pipelineIds: [],
+ checkProfile: 'inference-reproduction',
+ kind: 'inference',
+ },
+];
+
+export function worstResult(results: CheckResult[]): CheckResult {
+ if (results.includes('FAIL')) return 'FAIL';
+ if (results.includes('UNSUPPORTED')) return 'UNSUPPORTED';
+ if (results.includes('UNAVAILABLE')) return 'UNAVAILABLE';
+ if (results.length > 0 && results.every((r) => r === 'PASS')) return 'PASS';
+ if (results.includes('PASS') && results.some((r) => r !== 'PASS')) return 'NOT_RUN';
+ if (results.includes('NOT_RUN') || results.length === 0) return 'NOT_RUN';
+ return 'NOT_RUN';
+}
+
+export const WHY = [
+ {
+ title: 'A download is not a proof',
+ body: 'A hosted file and a checksum do not show which data was used or how training ran.',
+ },
+ {
+ title: 'A signature is not a replay',
+ body: 'A signature names who published a folder. It does not re-run training.',
+ },
+ {
+ title: 'Chat is not verification',
+ body: 'Generating text does not inspect data, checkpoints, or training.',
+ },
+];
+
+export const LAYERS = [
+ {
+ title: 'Evidence',
+ body: 'The public record: files, reports, and links the project published. This website displays that record. It does not re-run training.',
+ },
+ {
+ title: 'Verification',
+ body: 'Independent checks of that record — for example rebuilding the data or re-running training. Each check has its own meaning. There is no single green badge.',
+ },
+ {
+ title: 'Inference',
+ body: 'Using released weights to produce text. That is not enabled here, and it would not make answers into citations even if it were.',
+ },
+];
+
+export const FAQ = [
+ {
+ q: 'What is OpenVerifiableLLM?',
+ a: 'A project to train a small language model and keep a public record of how it was made: data, training steps, checkpoints (mid-training saves), and released weights (the downloadable model files). Other people should be able to inspect that record and later reproduce it. It is not a day-to-day chatbot.',
+ },
+ {
+ q: 'Why does that matter?',
+ a: 'Most open models give you files and a write-up. You can download the files. You usually cannot check the write-up. If a lab says “trained only on this Wikipedia dump,” you have to trust them. This project tries to make that checkable.',
+ },
+ {
+ q: 'What can I do on this website?',
+ a: 'Read the saved public record and follow links to original files. The Verify page explains what each kind of check would mean. This site does not train, sign, or generate text.',
+ },
+ {
+ q: 'What is a practice run versus the real run?',
+ a: 'A practice run (pilot) is the team testing the pipeline. The real run (production) is the Wikipedia-to-model release. A pass on a practice run does not finish the real run. Test data (fixture) is synthetic and is not project status.',
+ },
+ {
+ q: 'Is a pass here the same as the old ovllm GREEN verdict?',
+ a: 'No. An older command in the repo can print GREEN when file hashes and signatures match. That is artifact identity, not a full rebuild and re-run of Wikipedia training. This site keeps those checks separate.',
+ },
+ {
+ q: 'Who ran the checks shown?',
+ a: 'The project team, not an independent outsider, and not your browser. Opening this page does not recompute anything. If a check says it was not recomputed locally, that is correct.',
+ },
+ {
+ q: 'Would a later chat mean the answers are true?',
+ a: 'No. Checking how a model was made is not fact-checking what it says, and it does not mean a reply came from a particular Wikipedia article.',
+ },
+ {
+ q: 'Can I run the full check in the browser?',
+ a: 'No. Re-running training needs the stated computers and is slow and costly. It will never be a silent click here. A later, labelled checksum of a small published file would only prove that one file’s bytes.',
+ },
+ {
+ q: 'Where are the original files?',
+ a: 'On the Evidence page: GitHub and a public Hugging Face dataset. This website only shows those links. It does not host Wikipedia dumps or checkpoints.',
+ },
+];
diff --git a/frontend/src/data/adapters/snapshotAdapter.ts b/frontend/src/data/adapters/snapshotAdapter.ts
new file mode 100644
index 00000000..e885e345
--- /dev/null
+++ b/frontend/src/data/adapters/snapshotAdapter.ts
@@ -0,0 +1,31 @@
+import { SnapshotError, type Snapshot } from '../contracts';
+import { validateSnapshot } from '../validate';
+import emptyChecks from '../fixtures/empty-checks.json';
+import missingParent from '../fixtures/missing-parent.json';
+import publicSnapshot from '../fixtures/public-snapshot.json';
+import superseded from '../fixtures/superseded.json';
+
+export const SNAPSHOT_IDS = ['public', 'missing-parent', 'superseded', 'empty'] as const;
+export type SnapshotId = (typeof SNAPSHOT_IDS)[number];
+
+const RAW: Record = {
+ public: publicSnapshot,
+ 'missing-parent': missingParent,
+ superseded,
+ empty: emptyChecks,
+};
+
+export function loadSnapshot(id: SnapshotId = 'public'): Snapshot {
+ const raw = RAW[id];
+ if (raw === undefined) {
+ throw new SnapshotError(`Unknown snapshot ${id}`);
+ }
+ return validateSnapshot(raw);
+}
+
+export function parseSnapshotId(value: string | null): SnapshotId {
+ if (value && (SNAPSHOT_IDS as readonly string[]).includes(value)) {
+ return value as SnapshotId;
+ }
+ return 'public';
+}
diff --git a/frontend/src/data/contracts.ts b/frontend/src/data/contracts.ts
new file mode 100644
index 00000000..0b0bcb6c
--- /dev/null
+++ b/frontend/src/data/contracts.ts
@@ -0,0 +1,124 @@
+export const SNAPSHOT_SCHEMA = 'ovllm.frontend.snapshot.v1';
+
+export type CheckResult =
+ | 'PASS'
+ | 'FAIL'
+ | 'NOT_RUN'
+ | 'UNAVAILABLE'
+ | 'UNSUPPORTED';
+
+export type EvidenceScope = 'fixture' | 'pilot' | 'production';
+export type DataMode = 'fixture' | 'public-snapshot';
+export type ReleaseAvailability = 'not-released' | 'available' | 'withdrawn';
+export type ReleaseRole = 'base' | 'chat';
+
+export interface SourceReference {
+ url: string;
+ revision: string | null;
+ path: string | null;
+ sha256: string | null;
+}
+
+export interface CheckView {
+ id: string;
+ profile: string;
+ phase: string;
+ scope: EvidenceScope;
+ result: CheckResult;
+ explanation: string;
+ performedBy: string | null;
+ attestedBy: string | null;
+ locallyRecomputed: boolean;
+ evidence: SourceReference[];
+}
+
+export interface EvidenceItem {
+ id: string;
+ title: string;
+ kind: string;
+ phase: string;
+ scope: EvidenceScope;
+ result: CheckResult;
+ checkId: string | null;
+ source: SourceReference;
+ parentIds: string[];
+ childIds: string[];
+ sizeBytes: number | null;
+ timestamp: string | null;
+ supersededBy: string | null;
+ supersedes: string | null;
+}
+
+export interface PipelineStep {
+ id: string;
+ title: string;
+ phase: string;
+ result: CheckResult;
+ scope: EvidenceScope;
+ checkId: string | null;
+}
+
+export interface ReleaseView {
+ id: string;
+ role: ReleaseRole;
+ availability: ReleaseAvailability;
+ title: string;
+ parentReleaseId: string | null;
+ repository: string | null;
+ revision: string | null;
+ releaseRoot: string | null;
+ notes: string;
+}
+
+export interface CommandView {
+ profile: string;
+ text: string;
+ prerequisites: string;
+ sourceRevision: string | null;
+ expectedScope: string;
+ resourceNote: string;
+ validated: boolean;
+}
+
+export interface SnapshotSummary {
+ headline: string;
+ mayClaim: string[];
+ mayNotClaim: string[];
+ workflow: string;
+}
+
+export interface Snapshot {
+ schemaVersion: string;
+ generatedAt: string;
+ mode: DataMode;
+ source: SourceReference;
+ summary: SnapshotSummary;
+ pipeline: PipelineStep[];
+ releases: ReleaseView[];
+ checks: CheckView[];
+ evidence: EvidenceItem[];
+ commands: CommandView[];
+}
+
+export const CHECK_RESULTS: readonly CheckResult[] = [
+ 'PASS',
+ 'FAIL',
+ 'NOT_RUN',
+ 'UNAVAILABLE',
+ 'UNSUPPORTED',
+];
+
+export const SCOPES: readonly EvidenceScope[] = ['fixture', 'pilot', 'production'];
+export const DATA_MODES: readonly DataMode[] = ['fixture', 'public-snapshot'];
+export const AVAILABILITIES: readonly ReleaseAvailability[] = [
+ 'not-released',
+ 'available',
+ 'withdrawn',
+];
+
+export class SnapshotError extends Error {
+ constructor(message: string) {
+ super(message);
+ this.name = 'SnapshotError';
+ }
+}
diff --git a/frontend/src/data/fixtures/empty-checks.json b/frontend/src/data/fixtures/empty-checks.json
new file mode 100644
index 00000000..e132b15f
--- /dev/null
+++ b/frontend/src/data/fixtures/empty-checks.json
@@ -0,0 +1,22 @@
+{
+ "schemaVersion": "ovllm.frontend.snapshot.v1",
+ "generatedAt": "2026-09-19T12:00:00Z",
+ "mode": "fixture",
+ "source": {
+ "url": "https://github.com/AOSSIE-Org/OpenVerifiableLLM",
+ "revision": "fixture",
+ "path": "frontend/src/data/fixtures/empty-checks.json",
+ "sha256": null
+ },
+ "summary": {
+ "headline": "Test record with no checks.",
+ "mayClaim": [],
+ "mayNotClaim": ["anything"],
+ "workflow": "Must not render as success."
+ },
+ "pipeline": [],
+ "releases": [],
+ "checks": [],
+ "evidence": [],
+ "commands": []
+}
diff --git a/frontend/src/data/fixtures/missing-parent.json b/frontend/src/data/fixtures/missing-parent.json
new file mode 100644
index 00000000..424dea1f
--- /dev/null
+++ b/frontend/src/data/fixtures/missing-parent.json
@@ -0,0 +1,96 @@
+{
+ "schemaVersion": "ovllm.frontend.snapshot.v1",
+ "generatedAt": "2026-09-19T12:00:00Z",
+ "mode": "fixture",
+ "source": {
+ "url": "https://github.com/AOSSIE-Org/OpenVerifiableLLM",
+ "revision": "fixture",
+ "path": "frontend/src/data/fixtures/missing-parent.json",
+ "sha256": null
+ },
+ "summary": {
+ "headline": "Test record: an item points at an earlier file that is missing.",
+ "mayClaim": ["this is a synthetic fixture"],
+ "mayNotClaim": ["production status", "a released model"],
+ "workflow": "Used to test a missing link. Not real project status."
+ },
+ "pipeline": [
+ {
+ "id": "hash",
+ "title": "Artifact hash",
+ "phase": "source",
+ "result": "FAIL",
+ "scope": "fixture",
+ "checkId": "chk-hash"
+ }
+ ],
+ "releases": [
+ {
+ "id": "base",
+ "role": "base",
+ "availability": "not-released",
+ "title": "Base",
+ "parentReleaseId": null,
+ "repository": null,
+ "revision": null,
+ "releaseRoot": null,
+ "notes": "Fixture. Not a release."
+ },
+ {
+ "id": "chat",
+ "role": "chat",
+ "availability": "not-released",
+ "title": "Chat",
+ "parentReleaseId": "base",
+ "repository": null,
+ "revision": null,
+ "releaseRoot": null,
+ "notes": "Fixture. Not a release."
+ }
+ ],
+ "checks": [
+ {
+ "id": "chk-hash",
+ "profile": "artifact-identity",
+ "phase": "source",
+ "scope": "fixture",
+ "result": "FAIL",
+ "explanation": "Declared parent digest is absent from the snapshot.",
+ "performedBy": "fixture",
+ "attestedBy": "fixture",
+ "locallyRecomputed": false,
+ "evidence": [
+ {
+ "url": "https://github.com/AOSSIE-Org/OpenVerifiableLLM",
+ "revision": "fixture",
+ "path": "frontend/src/data/fixtures/missing-parent.json",
+ "sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ }
+ ]
+ }
+ ],
+ "evidence": [
+ {
+ "id": "ev-child",
+ "title": "Prepared stream (child)",
+ "kind": "artifact",
+ "phase": "prepare",
+ "scope": "fixture",
+ "result": "FAIL",
+ "checkId": "chk-hash",
+ "source": {
+ "url": "https://github.com/AOSSIE-Org/OpenVerifiableLLM",
+ "revision": "fixture",
+ "path": "missing-parent",
+ "sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
+ },
+ "parentIds": ["ev-parent-absent"],
+ "childIds": [],
+ "sizeBytes": 2048,
+ "timestamp": "2026-09-19T12:00:00Z",
+ "supersededBy": null,
+ "supersedes": null
+ }
+ ],
+ "commands": []
+}
diff --git a/frontend/src/data/fixtures/public-snapshot.json b/frontend/src/data/fixtures/public-snapshot.json
new file mode 100644
index 00000000..04991830
--- /dev/null
+++ b/frontend/src/data/fixtures/public-snapshot.json
@@ -0,0 +1,231 @@
+{
+ "schemaVersion": "ovllm.frontend.snapshot.v1",
+ "generatedAt": "2026-09-19T12:00:00Z",
+ "mode": "public-snapshot",
+ "source": {
+ "url": "https://github.com/AOSSIE-Org/OpenVerifiableLLM",
+ "revision": "03551ddedf9e54ae4355cf7e3f85c83c27e5f971",
+ "path": null,
+ "sha256": null
+ },
+ "summary": {
+ "headline": "OpenVerifiableLLM keeps a public record of a small language model’s data, training, checkpoints, and released files so other people can inspect and later reproduce them. The models are not released yet.",
+ "mayClaim": [
+ "The project team listed and downloaded the Wikipedia files for a practice run (G01–G02)"
+ ],
+ "mayNotClaim": [
+ "that a model is available to download or chat with",
+ "that training was fully re-run and matched",
+ "that someone outside the team has independently rebuilt it",
+ "that generated answers are true"
+ ],
+ "workflow": "The team has listed and fetched the Wikipedia files for a practice run (G01–G02). Training, a full re-run, and both models are not done yet."
+ },
+ "pipeline": [
+ {
+ "id": "wiki-source",
+ "title": "List the Wikipedia files to use",
+ "phase": "source",
+ "result": "PASS",
+ "scope": "pilot",
+ "checkId": "chk-g01"
+ },
+ {
+ "id": "wiki-acquire",
+ "title": "Download those Wikipedia files",
+ "phase": "source",
+ "result": "PASS",
+ "scope": "pilot",
+ "checkId": "chk-g02"
+ },
+ {
+ "id": "extract",
+ "title": "Turn Wikipedia pages into clean text",
+ "phase": "prepare",
+ "result": "NOT_RUN",
+ "scope": "production",
+ "checkId": null
+ },
+ {
+ "id": "tokenizer",
+ "title": "Turn that text into tokens the model can train on",
+ "phase": "prepare",
+ "result": "NOT_RUN",
+ "scope": "production",
+ "checkId": null
+ },
+ {
+ "id": "init",
+ "title": "Start the model from scratch (no hidden pretrained weights)",
+ "phase": "train",
+ "result": "NOT_RUN",
+ "scope": "production",
+ "checkId": null
+ },
+ {
+ "id": "wiki-train",
+ "title": "Train once on that Wikipedia text",
+ "phase": "train",
+ "result": "NOT_RUN",
+ "scope": "production",
+ "checkId": null
+ },
+ {
+ "id": "chat-train",
+ "title": "Then train for chat, using public conversation data",
+ "phase": "train",
+ "result": "NOT_RUN",
+ "scope": "production",
+ "checkId": null
+ },
+ {
+ "id": "replay",
+ "title": "Re-run training and check it matches the saved files",
+ "phase": "replay",
+ "result": "NOT_RUN",
+ "scope": "production",
+ "checkId": null
+ },
+ {
+ "id": "publish",
+ "title": "Publish the model files and the evidence",
+ "phase": "release",
+ "result": "NOT_RUN",
+ "scope": "production",
+ "checkId": null
+ }
+ ],
+ "releases": [
+ {
+ "id": "base",
+ "role": "base",
+ "availability": "not-released",
+ "title": "Base model (Wikipedia)",
+ "parentReleaseId": null,
+ "repository": null,
+ "revision": null,
+ "releaseRoot": null,
+ "notes": "The Wikipedia-trained model has not been published. Download and chat stay off until there is a public folder, a fixed version, and a published checksum for those files."
+ },
+ {
+ "id": "chat",
+ "role": "chat",
+ "availability": "not-released",
+ "title": "Chat model (built on the base model)",
+ "parentReleaseId": "base",
+ "repository": null,
+ "revision": null,
+ "releaseRoot": null,
+ "notes": "The chat model is trained after the Wikipedia model, so it is not “Wikipedia-only.” It is also not released."
+ }
+ ],
+ "checks": [
+ {
+ "id": "chk-g01",
+ "profile": "artifact-identity",
+ "phase": "source",
+ "scope": "pilot",
+ "result": "PASS",
+ "explanation": "The team listed the exact English Wikipedia files for a practice run and published links to that list. This only checks the file list, not training.",
+ "performedBy": "project-operator",
+ "attestedBy": "project-operator",
+ "locallyRecomputed": false,
+ "evidence": [
+ {
+ "url": "https://huggingface.co/datasets/AOSSIE/openverifiable-enwiki-20260901-20260918-r1-evidence",
+ "revision": null,
+ "path": null,
+ "sha256": null
+ }
+ ]
+ },
+ {
+ "id": "chk-g02",
+ "profile": "data-reconstruction",
+ "phase": "source",
+ "scope": "pilot",
+ "result": "PASS",
+ "explanation": "The team says the Wikipedia files used in the practice run are stored in a public archive. Someone outside the team has not yet rebuilt that from scratch.",
+ "performedBy": "project-operator",
+ "attestedBy": "project-operator",
+ "locallyRecomputed": false,
+ "evidence": [
+ {
+ "url": "https://github.com/AOSSIE-Org/OpenVerifiableLLM/pull/101",
+ "revision": null,
+ "path": null,
+ "sha256": null
+ }
+ ]
+ },
+ {
+ "id": "chk-replay",
+ "profile": "full-replay",
+ "phase": "replay",
+ "scope": "production",
+ "result": "NOT_RUN",
+ "explanation": "The real training has not been fully re-run and accepted. Practice GPU runs do not count as the finished real run.",
+ "performedBy": null,
+ "attestedBy": null,
+ "locallyRecomputed": false,
+ "evidence": []
+ },
+ {
+ "id": "chk-infer",
+ "profile": "inference-reproduction",
+ "phase": "release",
+ "scope": "production",
+ "result": "NOT_RUN",
+ "explanation": "No model files have been released, so there is nothing to re-run as a saved chat example.",
+ "performedBy": null,
+ "attestedBy": null,
+ "locallyRecomputed": false,
+ "evidence": []
+ }
+ ],
+ "evidence": [
+ {
+ "id": "ev-hf-dataset",
+ "title": "Public evidence files (practice run)",
+ "kind": "dataset",
+ "phase": "source",
+ "scope": "pilot",
+ "result": "PASS",
+ "checkId": "chk-g01",
+ "source": {
+ "url": "https://huggingface.co/datasets/AOSSIE/openverifiable-enwiki-20260901-20260918-r1-evidence",
+ "revision": null,
+ "path": null,
+ "sha256": null
+ },
+ "parentIds": [],
+ "childIds": ["ev-pr-101"],
+ "sizeBytes": null,
+ "timestamp": "2026-09-18T00:00:00Z",
+ "supersededBy": null,
+ "supersedes": null
+ },
+ {
+ "id": "ev-pr-101",
+ "title": "Training-pipeline work on GitHub (pull request 101)",
+ "kind": "report",
+ "phase": "source",
+ "scope": "pilot",
+ "result": "PASS",
+ "checkId": "chk-g02",
+ "source": {
+ "url": "https://github.com/AOSSIE-Org/OpenVerifiableLLM/pull/101",
+ "revision": null,
+ "path": null,
+ "sha256": null
+ },
+ "parentIds": ["ev-hf-dataset"],
+ "childIds": [],
+ "sizeBytes": null,
+ "timestamp": "2026-09-17T18:01:11Z",
+ "supersededBy": null,
+ "supersedes": null
+ }
+ ],
+ "commands": []
+}
diff --git a/frontend/src/data/fixtures/superseded.json b/frontend/src/data/fixtures/superseded.json
new file mode 100644
index 00000000..351363ed
--- /dev/null
+++ b/frontend/src/data/fixtures/superseded.json
@@ -0,0 +1,136 @@
+{
+ "schemaVersion": "ovllm.frontend.snapshot.v1",
+ "generatedAt": "2026-09-19T12:00:00Z",
+ "mode": "fixture",
+ "source": {
+ "url": "https://github.com/AOSSIE-Org/OpenVerifiableLLM",
+ "revision": "fixture",
+ "path": "frontend/src/data/fixtures/superseded.json",
+ "sha256": null
+ },
+ "summary": {
+ "headline": "Test record: an old failed check stays listed after a later pass.",
+ "mayClaim": ["this is a synthetic fixture"],
+ "mayNotClaim": ["production status"],
+ "workflow": "History is not rewritten. Not project status."
+ },
+ "pipeline": [
+ {
+ "id": "recon",
+ "title": "Data reconstruction",
+ "phase": "prepare",
+ "result": "PASS",
+ "scope": "fixture",
+ "checkId": "chk-recon-2"
+ }
+ ],
+ "releases": [
+ {
+ "id": "base",
+ "role": "base",
+ "availability": "not-released",
+ "title": "Base",
+ "parentReleaseId": null,
+ "repository": null,
+ "revision": null,
+ "releaseRoot": null,
+ "notes": "Fixture. Not a release."
+ },
+ {
+ "id": "chat",
+ "role": "chat",
+ "availability": "not-released",
+ "title": "Chat",
+ "parentReleaseId": "base",
+ "repository": null,
+ "revision": null,
+ "releaseRoot": null,
+ "notes": "Fixture. Not a release."
+ }
+ ],
+ "checks": [
+ {
+ "id": "chk-recon-1",
+ "profile": "data-reconstruction",
+ "phase": "prepare",
+ "scope": "fixture",
+ "result": "FAIL",
+ "explanation": "First reconstruction attempt did not match the committed root.",
+ "performedBy": "fixture",
+ "attestedBy": "fixture",
+ "locallyRecomputed": false,
+ "evidence": [
+ {
+ "url": "https://github.com/AOSSIE-Org/OpenVerifiableLLM",
+ "revision": "fixture",
+ "path": "report-1",
+ "sha256": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
+ }
+ ]
+ },
+ {
+ "id": "chk-recon-2",
+ "profile": "data-reconstruction",
+ "phase": "prepare",
+ "scope": "fixture",
+ "result": "PASS",
+ "explanation": "Later reconstruction matched the committed root. This does not erase the earlier FAIL.",
+ "performedBy": "fixture",
+ "attestedBy": "fixture",
+ "locallyRecomputed": false,
+ "evidence": [
+ {
+ "url": "https://github.com/AOSSIE-Org/OpenVerifiableLLM",
+ "revision": "fixture",
+ "path": "report-2",
+ "sha256": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
+ }
+ ]
+ }
+ ],
+ "evidence": [
+ {
+ "id": "ev-fail",
+ "title": "Reconstruction report v1",
+ "kind": "report",
+ "phase": "prepare",
+ "scope": "fixture",
+ "result": "FAIL",
+ "checkId": "chk-recon-1",
+ "source": {
+ "url": "https://github.com/AOSSIE-Org/OpenVerifiableLLM",
+ "revision": "fixture",
+ "path": "report-1",
+ "sha256": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
+ },
+ "parentIds": [],
+ "childIds": ["ev-pass"],
+ "sizeBytes": 512,
+ "timestamp": "2026-09-10T00:00:00Z",
+ "supersededBy": "ev-pass",
+ "supersedes": null
+ },
+ {
+ "id": "ev-pass",
+ "title": "Reconstruction report v2",
+ "kind": "report",
+ "phase": "prepare",
+ "scope": "fixture",
+ "result": "PASS",
+ "checkId": "chk-recon-2",
+ "source": {
+ "url": "https://github.com/AOSSIE-Org/OpenVerifiableLLM",
+ "revision": "fixture",
+ "path": "report-2",
+ "sha256": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
+ },
+ "parentIds": ["ev-fail"],
+ "childIds": [],
+ "sizeBytes": 640,
+ "timestamp": "2026-09-12T00:00:00Z",
+ "supersededBy": null,
+ "supersedes": "ev-fail"
+ }
+ ],
+ "commands": []
+}
diff --git a/frontend/src/data/validate.ts b/frontend/src/data/validate.ts
new file mode 100644
index 00000000..abf9fb3e
--- /dev/null
+++ b/frontend/src/data/validate.ts
@@ -0,0 +1,284 @@
+import {
+ AVAILABILITIES,
+ CHECK_RESULTS,
+ DATA_MODES,
+ SCOPES,
+ SNAPSHOT_SCHEMA,
+ SnapshotError,
+ type CheckResult,
+ type CheckView,
+ type CommandView,
+ type DataMode,
+ type EvidenceItem,
+ type EvidenceScope,
+ type PipelineStep,
+ type ReleaseAvailability,
+ type ReleaseRole,
+ type ReleaseView,
+ type Snapshot,
+ type SnapshotSummary,
+ type SourceReference,
+} from './contracts';
+
+const RESULT_ALIASES: Record = {
+ passed: 'PASS',
+ pass: 'PASS',
+ failed: 'FAIL',
+ fail: 'FAIL',
+ pending: 'NOT_RUN',
+ not_run: 'NOT_RUN',
+ unavailable: 'UNAVAILABLE',
+ unsupported: 'UNSUPPORTED',
+};
+
+function isRecord(value: unknown): value is Record {
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
+}
+
+function asString(value: unknown, field: string): string {
+ if (typeof value !== 'string' || value.trim() === '') {
+ throw new SnapshotError(`Missing or empty string: ${field}`);
+ }
+ return value;
+}
+
+function asNullableString(value: unknown, field: string): string | null {
+ if (value === null || value === undefined) return null;
+ if (typeof value !== 'string') {
+ throw new SnapshotError(`${field} must be a string or null`);
+ }
+ return value;
+}
+
+function asBoolean(value: unknown, field: string): boolean {
+ if (typeof value !== 'boolean') {
+ throw new SnapshotError(`${field} must be a boolean`);
+ }
+ return value;
+}
+
+function asNumberOrNull(value: unknown, field: string): number | null {
+ if (value === null || value === undefined) return null;
+ if (typeof value !== 'number' || Number.isNaN(value)) {
+ throw new SnapshotError(`${field} must be a number or null`);
+ }
+ return value;
+}
+
+function asStringList(value: unknown, field: string): string[] {
+ if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {
+ throw new SnapshotError(`${field} must be a list of strings`);
+ }
+ return value;
+}
+
+function parseResult(value: unknown, field: string): CheckResult {
+ if (typeof value !== 'string') {
+ throw new SnapshotError(`${field} is not a result string`);
+ }
+ if ((CHECK_RESULTS as readonly string[]).includes(value)) {
+ return value as CheckResult;
+ }
+ const alias = RESULT_ALIASES[value.toLowerCase()];
+ if (alias) return alias;
+ throw new SnapshotError(
+ `${field} value ${JSON.stringify(value)} is not a known check result`,
+ );
+}
+
+function parseScope(value: unknown, field: string): EvidenceScope {
+ if (typeof value !== 'string' || !(SCOPES as readonly string[]).includes(value)) {
+ throw new SnapshotError(`${field} must be fixture, pilot, or production`);
+ }
+ return value as EvidenceScope;
+}
+
+function parseMode(value: unknown): DataMode {
+ if (typeof value !== 'string' || !(DATA_MODES as readonly string[]).includes(value)) {
+ throw new SnapshotError('mode must be fixture or public-snapshot');
+ }
+ return value as DataMode;
+}
+
+function parseAvailability(value: unknown, field: string): ReleaseAvailability {
+ if (
+ typeof value !== 'string' ||
+ !(AVAILABILITIES as readonly string[]).includes(value)
+ ) {
+ throw new SnapshotError(`${field} is not a known availability`);
+ }
+ return value as ReleaseAvailability;
+}
+
+function parseSource(value: unknown, field: string): SourceReference {
+ if (!isRecord(value)) throw new SnapshotError(`${field} must be an object`);
+ const url = asString(value.url, `${field}.url`);
+ if (url.startsWith('javascript:') || url.startsWith('data:')) {
+ throw new SnapshotError(`${field}.url uses a disallowed scheme`);
+ }
+ return {
+ url,
+ revision: asNullableString(value.revision, `${field}.revision`),
+ path: asNullableString(value.path, `${field}.path`),
+ sha256: asNullableString(value.sha256, `${field}.sha256`),
+ };
+}
+
+function parseCheck(value: unknown, index: number): CheckView {
+ if (!isRecord(value)) throw new SnapshotError(`checks[${index}] must be an object`);
+ const result = parseResult(value.result, `checks[${index}].result`);
+ const check: CheckView = {
+ id: asString(value.id, `checks[${index}].id`),
+ profile: asString(value.profile, `checks[${index}].profile`),
+ phase: asString(value.phase, `checks[${index}].phase`),
+ scope: parseScope(value.scope, `checks[${index}].scope`),
+ result,
+ explanation: asString(value.explanation, `checks[${index}].explanation`),
+ performedBy: asNullableString(value.performedBy, `checks[${index}].performedBy`),
+ attestedBy: asNullableString(value.attestedBy, `checks[${index}].attestedBy`),
+ locallyRecomputed: asBoolean(
+ value.locallyRecomputed,
+ `checks[${index}].locallyRecomputed`,
+ ),
+ evidence: Array.isArray(value.evidence)
+ ? value.evidence.map((ref, j) => parseSource(ref, `checks[${index}].evidence[${j}]`))
+ : [],
+ };
+ if (result === 'PASS' && check.evidence.length === 0) {
+ throw new SnapshotError(`checks[${index}] PASS is missing evidence references`);
+ }
+ return check;
+}
+
+function parseEvidence(value: unknown, index: number): EvidenceItem {
+ if (!isRecord(value)) throw new SnapshotError(`evidence[${index}] must be an object`);
+ return {
+ id: asString(value.id, `evidence[${index}].id`),
+ title: asString(value.title, `evidence[${index}].title`),
+ kind: asString(value.kind, `evidence[${index}].kind`),
+ phase: asString(value.phase, `evidence[${index}].phase`),
+ scope: parseScope(value.scope, `evidence[${index}].scope`),
+ result: parseResult(value.result, `evidence[${index}].result`),
+ checkId: asNullableString(value.checkId, `evidence[${index}].checkId`),
+ source: parseSource(value.source, `evidence[${index}].source`),
+ parentIds: asStringList(value.parentIds ?? [], `evidence[${index}].parentIds`),
+ childIds: asStringList(value.childIds ?? [], `evidence[${index}].childIds`),
+ sizeBytes: asNumberOrNull(value.sizeBytes, `evidence[${index}].sizeBytes`),
+ timestamp: asNullableString(value.timestamp, `evidence[${index}].timestamp`),
+ supersededBy: asNullableString(value.supersededBy, `evidence[${index}].supersededBy`),
+ supersedes: asNullableString(value.supersedes, `evidence[${index}].supersedes`),
+ };
+}
+
+function parsePipeline(value: unknown, index: number): PipelineStep {
+ if (!isRecord(value)) throw new SnapshotError(`pipeline[${index}] must be an object`);
+ return {
+ id: asString(value.id, `pipeline[${index}].id`),
+ title: asString(value.title, `pipeline[${index}].title`),
+ phase: asString(value.phase, `pipeline[${index}].phase`),
+ result: parseResult(value.result, `pipeline[${index}].result`),
+ scope: parseScope(value.scope, `pipeline[${index}].scope`),
+ checkId: asNullableString(value.checkId, `pipeline[${index}].checkId`),
+ };
+}
+
+function parseRelease(value: unknown, index: number): ReleaseView {
+ if (!isRecord(value)) throw new SnapshotError(`releases[${index}] must be an object`);
+ const role = asString(value.role, `releases[${index}].role`);
+ if (role !== 'base' && role !== 'chat') {
+ throw new SnapshotError(`releases[${index}].role must be base or chat`);
+ }
+ return {
+ id: asString(value.id, `releases[${index}].id`),
+ role: role as ReleaseRole,
+ availability: parseAvailability(value.availability, `releases[${index}].availability`),
+ title: asString(value.title, `releases[${index}].title`),
+ parentReleaseId: asNullableString(
+ value.parentReleaseId,
+ `releases[${index}].parentReleaseId`,
+ ),
+ repository: asNullableString(value.repository, `releases[${index}].repository`),
+ revision: asNullableString(value.revision, `releases[${index}].revision`),
+ releaseRoot: asNullableString(value.releaseRoot, `releases[${index}].releaseRoot`),
+ notes: asString(value.notes, `releases[${index}].notes`),
+ };
+}
+
+function parseCommand(value: unknown, index: number): CommandView {
+ if (!isRecord(value)) throw new SnapshotError(`commands[${index}] must be an object`);
+ return {
+ profile: asString(value.profile, `commands[${index}].profile`),
+ text: asString(value.text, `commands[${index}].text`),
+ prerequisites: asString(value.prerequisites, `commands[${index}].prerequisites`),
+ sourceRevision: asNullableString(
+ value.sourceRevision,
+ `commands[${index}].sourceRevision`,
+ ),
+ expectedScope: asString(value.expectedScope, `commands[${index}].expectedScope`),
+ resourceNote: asString(value.resourceNote, `commands[${index}].resourceNote`),
+ validated: asBoolean(value.validated, `commands[${index}].validated`),
+ };
+}
+
+function parseSummary(value: unknown): SnapshotSummary {
+ if (!isRecord(value)) throw new SnapshotError('summary must be an object');
+ return {
+ headline: asString(value.headline, 'summary.headline'),
+ mayClaim: asStringList(value.mayClaim, 'summary.mayClaim'),
+ mayNotClaim: asStringList(value.mayNotClaim, 'summary.mayNotClaim'),
+ workflow: asString(value.workflow, 'summary.workflow'),
+ };
+}
+
+export function validateSnapshot(raw: unknown): Snapshot {
+ if (!isRecord(raw)) throw new SnapshotError('Snapshot must be a JSON object');
+ const schemaVersion = asString(raw.schemaVersion, 'schemaVersion');
+ if (schemaVersion !== SNAPSHOT_SCHEMA) {
+ throw new SnapshotError(
+ `Unsupported schema ${schemaVersion}; expected ${SNAPSHOT_SCHEMA}`,
+ );
+ }
+
+ const checks = Array.isArray(raw.checks)
+ ? raw.checks.map((item, i) => parseCheck(item, i))
+ : [];
+ const evidence = Array.isArray(raw.evidence)
+ ? raw.evidence.map((item, i) => parseEvidence(item, i))
+ : [];
+ const releases = Array.isArray(raw.releases)
+ ? raw.releases.map((item, i) => parseRelease(item, i))
+ : [];
+ const pipeline = Array.isArray(raw.pipeline)
+ ? raw.pipeline.map((item, i) => parsePipeline(item, i))
+ : [];
+ const commands = Array.isArray(raw.commands)
+ ? raw.commands.map((item, i) => parseCommand(item, i))
+ : [];
+
+ if (checks.length === 0) {
+ throw new SnapshotError(
+ 'Snapshot has no checks; an empty docket cannot be shown as success',
+ );
+ }
+
+ return {
+ schemaVersion,
+ generatedAt: asString(raw.generatedAt, 'generatedAt'),
+ mode: parseMode(raw.mode),
+ source: parseSource(raw.source, 'source'),
+ summary: parseSummary(raw.summary),
+ pipeline,
+ releases,
+ checks,
+ evidence,
+ commands,
+ };
+}
+
+export function releaseIsDownloadable(release: ReleaseView): boolean {
+ return (
+ release.availability === 'available' &&
+ Boolean(release.repository) &&
+ Boolean(release.revision)
+ );
+}
diff --git a/frontend/src/inference/DisabledAdapter.ts b/frontend/src/inference/DisabledAdapter.ts
new file mode 100644
index 00000000..818bb34b
--- /dev/null
+++ b/frontend/src/inference/DisabledAdapter.ts
@@ -0,0 +1,17 @@
+import { GenerationUnavailable, type InferenceAdapter, type InferenceCapabilities } from './types';
+
+export class DisabledAdapter implements InferenceAdapter {
+ async capabilities(): Promise {
+ return {
+ mock: false,
+ available: false,
+ releaseIds: [],
+ modes: ['completion', 'conversation'],
+ note: 'Generation is unavailable until a verified model release is connected',
+ };
+ }
+
+ async generate(): Promise {
+ throw new GenerationUnavailable();
+ }
+}
diff --git a/frontend/src/inference/MockAdapter.ts b/frontend/src/inference/MockAdapter.ts
new file mode 100644
index 00000000..d3895695
--- /dev/null
+++ b/frontend/src/inference/MockAdapter.ts
@@ -0,0 +1,40 @@
+import {
+ IdentityMismatch,
+ type GenerationRequest,
+ type GenerationResponse,
+ type InferenceAdapter,
+ type InferenceCapabilities,
+} from './types';
+
+const DISCLAIMER =
+ 'Example UI response — not generated by a released OpenVerifiableLLM model';
+
+export class MockAdapter implements InferenceAdapter {
+ private readonly forceWrongId: boolean;
+
+ constructor(forceWrongId = false) {
+ this.forceWrongId = forceWrongId;
+ }
+
+ async capabilities(): Promise {
+ return {
+ mock: true,
+ available: true,
+ releaseIds: ['fixture-base'],
+ modes: ['completion', 'conversation'],
+ note: DISCLAIMER,
+ };
+ }
+
+ async generate(request: GenerationRequest): Promise {
+ const releaseId = this.forceWrongId ? 'other-release' : request.releaseId;
+ if (releaseId !== request.releaseId) {
+ throw new IdentityMismatch();
+ }
+ return {
+ text: 'This is a scripted fixture reply so the composer can be tested.',
+ releaseId,
+ disclaimer: DISCLAIMER,
+ };
+ }
+}
diff --git a/frontend/src/inference/types.ts b/frontend/src/inference/types.ts
new file mode 100644
index 00000000..dbf0ba74
--- /dev/null
+++ b/frontend/src/inference/types.ts
@@ -0,0 +1,41 @@
+export type InferenceMode = 'completion' | 'conversation';
+
+export interface InferenceCapabilities {
+ mock: boolean;
+ available: boolean;
+ releaseIds: string[];
+ modes: InferenceMode[];
+ note: string;
+}
+
+export interface GenerationRequest {
+ releaseId: string;
+ mode: InferenceMode;
+ prompt?: string;
+ messages?: { role: 'user' | 'assistant'; content: string }[];
+}
+
+export interface GenerationResponse {
+ text: string;
+ releaseId: string;
+ disclaimer: string;
+}
+
+export class GenerationUnavailable extends Error {
+ constructor(message = 'Generation is unavailable until a verified model release is connected') {
+ super(message);
+ this.name = 'GenerationUnavailable';
+ }
+}
+
+export class IdentityMismatch extends Error {
+ constructor() {
+ super('Adapter returned a different release identity than the one selected');
+ this.name = 'IdentityMismatch';
+ }
+}
+
+export interface InferenceAdapter {
+ capabilities(): Promise;
+ generate(request: GenerationRequest, signal?: AbortSignal): Promise;
+}
diff --git a/frontend/src/layout/AppShell.tsx b/frontend/src/layout/AppShell.tsx
new file mode 100644
index 00000000..7d5918a2
--- /dev/null
+++ b/frontend/src/layout/AppShell.tsx
@@ -0,0 +1,91 @@
+import { Link, NavLink, Outlet } from 'react-router-dom';
+import { SNAPSHOT_IDS } from '../data/adapters/snapshotAdapter';
+import { useSnapSearch } from '../nav';
+import { useSnapshotState } from '../snapshot';
+import { ClaimStrip } from './ClaimStrip';
+
+const LINKS = [
+ { to: '/', label: 'Overview', end: true },
+ { to: '/evidence', label: 'Evidence' },
+ { to: '/releases', label: 'Release' },
+ { to: '/verify', label: 'Verify' },
+ { to: '/try', label: 'Inference' },
+];
+
+export function AppShell() {
+ const { snapshot, error, id } = useSnapshotState();
+ const search = useSnapSearch();
+
+ return (
+ <>
+
+ Skip to content
+
+
+
+ {snapshot?.mode === 'fixture' ? (
+ Test data — not the real project status
+ ) : null}
+
+ {snapshot ? : null}
+
+
+ {error ? (
+
+ Data error
+ This record cannot be shown as a pass
+ {error.message}
+
+ If the saved record is empty or broken, that is an error — not a successful check.
+
+
+ ) : (
+
+ )}
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/layout/ClaimStrip.tsx b/frontend/src/layout/ClaimStrip.tsx
new file mode 100644
index 00000000..7ab40233
--- /dev/null
+++ b/frontend/src/layout/ClaimStrip.tsx
@@ -0,0 +1,26 @@
+import type { Snapshot } from '../data/contracts';
+
+export function ClaimStrip({ snapshot }: { snapshot: Snapshot }) {
+ const revision = snapshot.source.revision ?? 'unspecified';
+ return (
+
+
+
+
What we can say today
+
+
+ {snapshot.summary.mayClaim.join(' · ') || 'nothing beyond the snapshot'}
+
+
+
+
+
What we cannot say
+
{snapshot.summary.mayNotClaim.join(' · ')}
+
+
+ Saved {snapshot.generatedAt} · {revision} · this website did not re-run the checks
+
+
+
+ );
+}
diff --git a/frontend/src/layout/StatusMark.tsx b/frontend/src/layout/StatusMark.tsx
new file mode 100644
index 00000000..a3fb2c6d
--- /dev/null
+++ b/frontend/src/layout/StatusMark.tsx
@@ -0,0 +1,26 @@
+import type { CheckResult } from '../data/contracts';
+
+const LABELS: Record = {
+ PASS: 'PASS',
+ FAIL: 'FAIL',
+ NOT_RUN: 'NOT RUN',
+ UNAVAILABLE: 'UNAVAILABLE',
+ UNSUPPORTED: 'UNSUPPORTED',
+};
+
+export function StatusMark({ result }: { result: CheckResult }) {
+ const tone =
+ result === 'PASS' ? 'pass' : result === 'FAIL' ? 'fail' : 'hold';
+ return (
+
+
+ {LABELS[result]}
+
+ );
+}
+
+export function pipelineClass(result: CheckResult): string {
+ if (result === 'PASS') return 'pass';
+ if (result === 'FAIL') return 'fail';
+ return 'hold';
+}
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
new file mode 100644
index 00000000..82a4f9ec
--- /dev/null
+++ b/frontend/src/main.tsx
@@ -0,0 +1,13 @@
+import { StrictMode } from 'react';
+import { createRoot } from 'react-dom/client';
+import App from './App';
+import './styles/base.css';
+
+const root = document.getElementById('root');
+if (!root) throw new Error('root element missing');
+
+createRoot(root).render(
+
+
+ ,
+);
diff --git a/frontend/src/nav.ts b/frontend/src/nav.ts
new file mode 100644
index 00000000..cdbd3184
--- /dev/null
+++ b/frontend/src/nav.ts
@@ -0,0 +1,7 @@
+import { useSearchParams } from 'react-router-dom';
+
+export function useSnapSearch(): string {
+ const [params] = useSearchParams();
+ const search = params.toString();
+ return search ? `?${search}` : '';
+}
diff --git a/frontend/src/pages/EvidenceDetailPage.tsx b/frontend/src/pages/EvidenceDetailPage.tsx
new file mode 100644
index 00000000..8668af55
--- /dev/null
+++ b/frontend/src/pages/EvidenceDetailPage.tsx
@@ -0,0 +1,138 @@
+import { useState } from 'react';
+import { Link, useParams } from 'react-router-dom';
+import { PHASE_LABEL, SCOPE_LABEL } from '../content/copy';
+import { StatusMark } from '../layout/StatusMark';
+import { useSnapSearch } from '../nav';
+import { useSnapshot } from '../snapshot';
+
+export function EvidenceDetailPage() {
+ const { id } = useParams();
+ const snapshot = useSnapshot();
+ const search = useSnapSearch();
+ const item = snapshot.evidence.find((row) => row.id === id);
+ const check = item?.checkId
+ ? snapshot.checks.find((row) => row.id === item.checkId)
+ : undefined;
+ const [copied, setCopied] = useState(false);
+
+ if (!item) {
+ return (
+
+ This item is not in the record
+ A missing item is shown as missing — not as a passed check.
+
+ Back to evidence
+
+
+ );
+ }
+
+ const digest = item.source.sha256;
+ const knownIds = new Set(snapshot.evidence.map((row) => row.id));
+
+ async function copyDigest() {
+ if (!digest) return;
+ await navigator.clipboard.writeText(digest);
+ setCopied(true);
+ }
+
+ return (
+
+
+ Evidence
+
+ {item.title}
+
+ · {SCOPE_LABEL[item.scope]} · {item.kind} ·{' '}
+ {PHASE_LABEL[item.phase] ?? item.phase}
+
+ {item.supersededBy ? (
+
+ Replaced by{' '}
+ {item.supersededBy}
+
+ ) : null}
+ {item.supersedes ? (
+
+ Replaces {item.supersedes}
+
+ ) : null}
+
+ What this sits on
+
+ {item.parentIds.length === 0 ? (
+ No earlier item linked
+ ) : (
+ item.parentIds.map((parent) =>
+ knownIds.has(parent) ? (
+
+ Comes from: {parent}
+
+ ) : (
+
+ Missing link — {parent} is not in this record
+
+ ),
+ )
+ )}
+
+ This item: {item.id}
+
+ {item.childIds.map((child) => (
+
+ Used by:{' '}
+ {knownIds.has(child) ? (
+ {child}
+ ) : (
+ child
+ )}
+
+ ))}
+
+
+ Original location
+
+
+ {item.source.url}
+
+
+ {item.source.path ? Path: {item.source.path}
: null}
+ {item.source.revision ? (
+ Revision {item.source.revision}
+ ) : null}
+ {digest ? (
+
+ {digest} {' '}
+ void copyDigest()}>
+ {copied ? 'Copied digest' : 'Copy digest'}
+
+
+ ) : (
+ No checksum (SHA-256) is listed in this record.
+ )}
+ {item.timestamp ? (
+ Timestamp {item.timestamp} (UTC as published)
+ ) : null}
+
+ {check ? (
+ <>
+ Linked check
+ {check.explanation}
+
+ Who ran it (technical)
+
+ Check type: {check.profile}. Run by {check.performedBy ?? 'not listed'}.
+ Signed off by {check.attestedBy ?? 'not listed'}. Recomputed in this browser:{' '}
+ {check.locallyRecomputed ? 'yes' : 'no'}.
+
+
+ >
+ ) : null}
+
+
+ Raw JSON (display only)
+ {JSON.stringify(item, null, 2)}
+
+
+ );
+}
diff --git a/frontend/src/pages/EvidenceListPage.tsx b/frontend/src/pages/EvidenceListPage.tsx
new file mode 100644
index 00000000..01c97409
--- /dev/null
+++ b/frontend/src/pages/EvidenceListPage.tsx
@@ -0,0 +1,196 @@
+import { useMemo, useState } from 'react';
+import { Link } from 'react-router-dom';
+import {
+ CHECK_RESULTS,
+ SCOPES,
+ type CheckResult,
+ type EvidenceItem,
+} from '../data/contracts';
+import { PHASE_LABEL, SCOPE_LABEL } from '../content/copy';
+import { StatusMark } from '../layout/StatusMark';
+import { useSnapSearch } from '../nav';
+import { useSnapshot } from '../snapshot';
+
+const PHASE_ORDER = [
+ 'source',
+ 'prepare',
+ 'train',
+ 'replay',
+ 'release',
+ 'other',
+] as const;
+
+const PHASE_TITLE: Record = {
+ source: PHASE_LABEL.source,
+ prepare: PHASE_LABEL.prepare,
+ train: PHASE_LABEL.train,
+ replay: PHASE_LABEL.replay,
+ release: PHASE_LABEL.release,
+ other: PHASE_LABEL.other,
+};
+
+function formatSize(bytes: number | null): string {
+ if (bytes === null) return '—';
+ if (bytes < 1024) return `${bytes} B`;
+ return `${(bytes / 1024).toFixed(1)} KB`;
+}
+
+function phaseKey(phase: string): string {
+ return (PHASE_ORDER as readonly string[]).includes(phase) ? phase : 'other';
+}
+
+function Table({ rows, search }: { rows: EvidenceItem[]; search: string }) {
+ if (rows.length === 0) return No rows in this group.
;
+ return (
+
+
+
+ Title
+ Kind
+ Scope
+ Result
+ When
+ Size
+
+
+
+ {rows.map((item) => (
+
+
+
+ {item.title}
+
+
+ {item.kind}
+ {SCOPE_LABEL[item.scope]}
+
+
+
+ {item.timestamp ?? '—'}
+ {formatSize(item.sizeBytes)}
+
+ ))}
+
+
+ );
+}
+
+export function EvidenceListPage() {
+ const snapshot = useSnapshot();
+ const search = useSnapSearch();
+ const [result, setResult] = useState('all');
+ const [scope, setScope] = useState('all');
+ const [kind, setKind] = useState('all');
+ const [phase, setPhase] = useState('all');
+
+ const kinds = useMemo(
+ () => [...new Set(snapshot.evidence.map((item) => item.kind))].sort(),
+ [snapshot.evidence],
+ );
+ const phases = useMemo(
+ () => [...new Set(snapshot.evidence.map((item) => item.phase))].sort(),
+ [snapshot.evidence],
+ );
+
+ const filtered = useMemo(() => {
+ return snapshot.evidence.filter((item) => {
+ if (result !== 'all' && item.result !== result) return false;
+ if (scope !== 'all' && item.scope !== scope) return false;
+ if (kind !== 'all' && item.kind !== kind) return false;
+ if (phase !== 'all' && item.phase !== phase) return false;
+ return true;
+ });
+ }, [snapshot.evidence, result, scope, kind, phase]);
+
+ const grouped = PHASE_ORDER.map((key) => ({
+ key,
+ title: PHASE_TITLE[key],
+ rows: filtered.filter((item) => phaseKey(item.phase) === key),
+ })).filter((g) => g.rows.length > 0);
+
+ const pass = filtered.filter((i) => i.result === 'PASS').length;
+
+ return (
+
+ Public record
+ Evidence
+
+ What. The public record: files and reports the project published.
+
+
+ Why. Evidence is what verification later checks. This page only shows
+ links; it does not re-run training or generate text.
+
+
+ Current status. Rows below are this saved record. Open one for who ran the
+ check and the technical fields.
+
+
+
+ {filtered.length}
+ items shown
+
+
+ {pass}
+ PASS in view
+
+
+
+
+ Result
+ setResult(e.target.value)}>
+ All
+ {CHECK_RESULTS.map((item) => (
+
+ {item}
+
+ ))}
+
+
+
+ Scope
+ setScope(e.target.value)}>
+ All
+ {SCOPES.map((item) => (
+
+ {SCOPE_LABEL[item]}
+
+ ))}
+
+
+
+ Kind
+ setKind(e.target.value)}>
+ All
+ {kinds.map((item) => (
+
+ {item}
+
+ ))}
+
+
+
+ Phase
+ setPhase(e.target.value)}>
+ All
+ {phases.map((item) => (
+
+ {item}
+
+ ))}
+
+
+
+ {filtered.length === 0 ? (
+ No rows match these filters. That is not a passed check.
+ ) : (
+ grouped.map((group) => (
+
+ ))
+ )}
+
+ );
+}
diff --git a/frontend/src/pages/Journey.tsx b/frontend/src/pages/Journey.tsx
new file mode 100644
index 00000000..f232817c
--- /dev/null
+++ b/frontend/src/pages/Journey.tsx
@@ -0,0 +1,61 @@
+import { JOURNEY, SCOPE_LABEL, worstResult } from '../content/copy';
+import type { CheckResult, PipelineStep, Snapshot } from '../data/contracts';
+import { StatusMark } from '../layout/StatusMark';
+
+function stageResult(snapshot: Snapshot, pipelineIds: string[], checkProfile: string | null): CheckResult {
+ const fromSteps = snapshot.pipeline
+ .filter((step) => pipelineIds.includes(step.id))
+ .map((step) => step.result);
+ const fromCheck = checkProfile
+ ? snapshot.checks.filter((c) => c.profile === checkProfile).map((c) => c.result)
+ : [];
+ return worstResult([...fromSteps, ...fromCheck]);
+}
+
+function stepsFor(snapshot: Snapshot, ids: string[]): PipelineStep[] {
+ return ids
+ .map((id) => snapshot.pipeline.find((step) => step.id === id))
+ .filter((step): step is PipelineStep => Boolean(step));
+}
+
+const KIND_LINE = {
+ evidence: 'Project evidence',
+ verification: 'Verification',
+ inference: 'Inference',
+} as const;
+
+export function Journey({ snapshot }: { snapshot: Snapshot }) {
+ return (
+
+ {JOURNEY.map((stage) => {
+ const result = stageResult(snapshot, stage.pipelineIds, stage.checkProfile);
+ const steps = stepsFor(snapshot, stage.pipelineIds);
+ return (
+
+ {stage.label}
+
+ {stage.what}
+
+ Details
+ {stage.why}
+ {KIND_LINE[stage.kind]}
+ {steps.length === 0 && stage.checkProfile ? (
+
+ Check: {stage.checkProfile}
+
+ ) : null}
+
+ {steps.map((step) => (
+
+ {step.title} — · {SCOPE_LABEL[step.scope]} ·{' '}
+ {step.id}
+
+ ))}
+
+
+
+ );
+ })}
+
+ );
+}
diff --git a/frontend/src/pages/OverviewPage.tsx b/frontend/src/pages/OverviewPage.tsx
new file mode 100644
index 00000000..7b581758
--- /dev/null
+++ b/frontend/src/pages/OverviewPage.tsx
@@ -0,0 +1,75 @@
+import { Link } from 'react-router-dom';
+import { WHY } from '../content/copy';
+import { Journey } from './Journey';
+import { useSnapSearch } from '../nav';
+import { useSnapshot } from '../snapshot';
+
+export function OverviewPage() {
+ const snapshot = useSnapshot();
+ const search = useSnapSearch();
+ const pass = snapshot.checks.filter((c) => c.result === 'PASS').length;
+ const fail = snapshot.checks.filter((c) => c.result === 'FAIL').length;
+ const pending = snapshot.checks.filter((c) => c.result !== 'PASS' && c.result !== 'FAIL').length;
+ const released = snapshot.releases.filter((r) => r.availability === 'available').length;
+
+ return (
+
+
+
+
AOSSIE · OpenVerifiableLLM
+
A public record of how a small open model is made.
+
+ Data, training, checkpoints, and released weights — inspectable, and later
+ reproducible. This site shows the record; it does not train or chat.
+
+
+
+ Evidence
+
+
+ Verification
+
+
+ Inference
+
+
+
+
+
+
+ Why
+
+ {WHY.map((item) => (
+
+ {item.title}
+ {item.body}
+
+ ))}
+
+
+ Data → Preparation → Training → Replay → Release → Inference
+
+
+ );
+}
diff --git a/frontend/src/pages/ReleasesPage.tsx b/frontend/src/pages/ReleasesPage.tsx
new file mode 100644
index 00000000..dbdaa518
--- /dev/null
+++ b/frontend/src/pages/ReleasesPage.tsx
@@ -0,0 +1,63 @@
+import { releaseIsDownloadable } from '../data/validate';
+import { useSnapshot } from '../snapshot';
+
+export function ReleasesPage() {
+ const snapshot = useSnapshot();
+ return (
+
+ Release
+ Model files
+
+ What. Two published models are planned: a base model trained on Wikipedia,
+ then a chat model trained on top of it.
+
+
+ Why. Without a public folder, a fixed version, and checksums, there are no
+ weights for anyone else to download or check.
+
+
+ Current status. Neither release is listed. Download and generate stay off.
+ Listing files here later would still not mean complete replay had passed.
+
+
+ {snapshot.releases.map((release) => {
+ const ready = releaseIsDownloadable(release);
+ const isChat = release.role === 'chat';
+ return (
+
+ {isChat ? 'Chat model' : 'Base model'}
+ {release.title}
+
+ Status: {release.availability === 'not-released' ? 'not released' : release.availability}
+
+ {release.parentReleaseId ? (
+ Depends on the base Wikipedia model.
+ ) : (
+ Wikipedia-trained model, when published.
+ )}
+ {release.notes}
+
+ Technical fields
+
+ Repository: {release.repository ?? 'not set'}
+
+ Revision: {release.revision ?? 'not set'}
+
+ Release root: {release.releaseRoot ?? 'not set'}
+
+
+
+
+ Download
+ {' '}
+
+ Generate
+
+
+
+ );
+ })}
+
+
+ );
+}
diff --git a/frontend/src/pages/TryPage.tsx b/frontend/src/pages/TryPage.tsx
new file mode 100644
index 00000000..7eae5e1b
--- /dev/null
+++ b/frontend/src/pages/TryPage.tsx
@@ -0,0 +1,45 @@
+import { useSnapshot } from '../snapshot';
+
+export function TryPage() {
+ const snapshot = useSnapshot();
+ const chat = snapshot.releases.find((item) => item.role === 'chat');
+
+ return (
+
+ Inference
+ Try
+
+ What. Inference is using released model files to produce text — including a
+ later, locked chat box on this page.
+
+
+ Why. That is a different claim from evidence (the public record) and from
+ verification (rebuilding or re-running training). A live chat would not inspect data or
+ checkpoints, and it would not make answers true.
+
+
+ Current status. Chat model: {chat?.availability === 'available' ? 'listed' : 'not released'}.
+ Send is off. This page will not fall back to another model.
+
+
+
+ Chat
+
+
+ Message
+
+
+
+ Send
+
+
+
+ Technical note
+
+ A labelled mock adapter exists in tests only. The public build does not call it.
+ Inference reproduction, when it exists, is a saved-example check — not this composer.
+
+
+
+ );
+}
diff --git a/frontend/src/pages/VerifyPage.tsx b/frontend/src/pages/VerifyPage.tsx
new file mode 100644
index 00000000..9543fd4a
--- /dev/null
+++ b/frontend/src/pages/VerifyPage.tsx
@@ -0,0 +1,109 @@
+import type { CheckResult } from '../data/contracts';
+import { FAQ, LAYERS, SCOPE_LABEL } from '../content/copy';
+import { StatusMark } from '../layout/StatusMark';
+import { useSnapshot } from '../snapshot';
+
+const PROFILES: {
+ id: string;
+ title: string;
+ means: string;
+ doesNot: string;
+ technical: string;
+}[] = [
+ {
+ id: 'artifact-identity',
+ title: 'Artifact identity',
+ means: 'The published files match their listed checksums, and they were published by the expected project account.',
+ doesNot: 'Does not prove how those files were trained.',
+ technical: 'artifact and publisher identity',
+ },
+ {
+ id: 'data-reconstruction',
+ title: 'Data reconstruction',
+ means: 'Starting from the public source files, the same cleaned training text can be produced.',
+ doesNot: 'Does not prove the downloaded model is the result of that training.',
+ technical: 'data reconstruction',
+ },
+ {
+ id: 'sampled-replay',
+ title: 'Sampled replay',
+ means: 'Only the listed slice of training was run again in the stated setup and matched.',
+ doesNot: 'Does not replace a complete replay.',
+ technical: 'sampled replay',
+ },
+ {
+ id: 'full-replay',
+ title: 'Complete replay',
+ means: 'Every listed step from data through training was rebuilt and run again on the stated computers, and the files matched.',
+ doesNot: 'This is not a click in the browser. It needs the stated machines and time. A cost estimate is not a bill.',
+ technical: 'complete end-to-end replay',
+ },
+ {
+ id: 'inference-reproduction',
+ title: 'Inference reproduction',
+ means: 'Using the identified model files and settings, a saved example output comes out again.',
+ doesNot: 'A live chat box is not this check, and it does not make answers true.',
+ technical: 'inference reproduction',
+ },
+];
+
+export function VerifyPage() {
+ const snapshot = useSnapshot();
+ return (
+
+ Verification
+ What each check means
+
+ Verification is not the same as the project’s evidence record, and not the same as
+ inference. Each profile below is a different claim. This page explains them; it does not
+ run them. No copy-paste command is shown until the maintainer supplies one that has been
+ tested.
+
+ {PROFILES.map((profile) => {
+ const check = snapshot.checks.find((item) => item.profile === profile.id);
+ const result: CheckResult = check?.result ?? 'NOT_RUN';
+ return (
+
+ {profile.title}
+
+ ·{' '}
+ {check ? SCOPE_LABEL[check.scope] : 'not in this record'}
+
+
+ If this passes: {profile.means}
+
+
+ It does not mean: {profile.doesNot}
+
+ {check ? {check.explanation}
: This record has no check of this kind yet.
}
+
+ Technical name
+ {profile.technical}
+
+ How to run it: instructions pending — no invented command.
+
+ );
+ })}
+
+ Evidence, verification, inference
+
+ {LAYERS.map((item) => (
+
+ {item.title}
+ {item.body}
+
+ ))}
+
+
+ Questions
+
+ {FAQ.map((item) => (
+
+ {item.q}
+ {item.a}
+
+ ))}
+
+
+ );
+}
diff --git a/frontend/src/snapshot.tsx b/frontend/src/snapshot.tsx
new file mode 100644
index 00000000..40df8c1d
--- /dev/null
+++ b/frontend/src/snapshot.tsx
@@ -0,0 +1,53 @@
+import {
+ createContext,
+ useContext,
+ useMemo,
+ type ReactNode,
+} from 'react';
+import { useSearchParams } from 'react-router-dom';
+import {
+ loadSnapshot,
+ parseSnapshotId,
+ type SnapshotId,
+} from './data/adapters/snapshotAdapter';
+import { SnapshotError, type Snapshot } from './data/contracts';
+
+interface SnapshotState {
+ snapshot: Snapshot | null;
+ error: SnapshotError | null;
+ id: SnapshotId;
+}
+
+const Ctx = createContext(null);
+
+export function SnapshotProvider({ children }: { children: ReactNode }) {
+ const [params] = useSearchParams();
+ const id = parseSnapshotId(params.get('snapshot'));
+ const value = useMemo(() => {
+ try {
+ return { snapshot: loadSnapshot(id), error: null, id };
+ } catch (err) {
+ const error =
+ err instanceof SnapshotError
+ ? err
+ : new SnapshotError(err instanceof Error ? err.message : 'Invalid snapshot');
+ return { snapshot: null, error, id };
+ }
+ }, [id]);
+
+ return {children} ;
+}
+
+export function useSnapshotState(): SnapshotState {
+ const ctx = useContext(Ctx);
+ if (!ctx) throw new Error('SnapshotProvider missing');
+ return ctx;
+}
+
+export function useSnapshot(): Snapshot {
+ const { snapshot, error } = useSnapshotState();
+ if (error || !snapshot) {
+ throw error ?? new SnapshotError('Snapshot unavailable');
+ }
+ return snapshot;
+}
diff --git a/frontend/src/styles/base.css b/frontend/src/styles/base.css
new file mode 100644
index 00000000..fba71002
--- /dev/null
+++ b/frontend/src/styles/base.css
@@ -0,0 +1,675 @@
+@import './tokens.css';
+
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+}
+
+html {
+ scroll-behavior: smooth;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ html {
+ scroll-behavior: auto;
+ }
+ *,
+ *::before,
+ *::after {
+ animation: none !important;
+ transition: none !important;
+ }
+}
+
+body {
+ margin: 0;
+ background: var(--bg);
+ color: var(--ink);
+ font-family: var(--sans);
+ font-size: 1.05rem;
+ line-height: 1.55;
+}
+
+#root {
+ min-height: 100vh;
+}
+
+a {
+ color: var(--link);
+}
+
+a:focus-visible,
+button:focus-visible,
+select:focus-visible,
+summary:focus-visible {
+ outline: 2px solid var(--gold);
+ outline-offset: 3px;
+}
+
+.skip {
+ position: absolute;
+ left: 0.75rem;
+ top: -3.5rem;
+ z-index: 20;
+ background: var(--nav);
+ color: var(--nav-ink);
+ padding: 0.45rem 0.75rem;
+}
+
+.skip:focus {
+ top: 0.75rem;
+}
+
+.mast {
+ background: var(--nav);
+ color: var(--nav-ink);
+ position: sticky;
+ top: 0;
+ z-index: 10;
+}
+
+.mast-inner,
+.shell,
+.foot-inner {
+ max-width: var(--wide);
+ margin: 0 auto;
+ padding: 0 1.25rem;
+}
+
+.mast-inner {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: space-between;
+ gap: 0.75rem 1.5rem;
+ min-height: 4rem;
+}
+
+.wordmark {
+ color: inherit;
+ text-decoration: none;
+ font-family: var(--serif);
+ font-weight: 600;
+ font-size: 1.15rem;
+}
+
+.wordmark em {
+ font-style: normal;
+ color: var(--gold);
+}
+
+.wordmark span {
+ display: block;
+ font-family: var(--sans);
+ font-size: 0.72rem;
+ font-weight: 400;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: #b7c0b8;
+}
+
+.mast nav {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.35rem 0.2rem;
+}
+
+.mast nav a {
+ color: #d7ddd8;
+ text-decoration: none;
+ padding: 0.4rem 0.7rem;
+ border-radius: 999px;
+ font-size: 0.92rem;
+}
+
+.mast nav a:hover,
+.mast nav a[aria-current='page'] {
+ color: var(--nav);
+ background: var(--gold);
+}
+
+.tape {
+ margin: 0;
+ padding: 0.4rem 1.25rem;
+ background: var(--tape);
+ color: #fff;
+ font-size: 0.8rem;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ text-align: center;
+}
+
+.claim {
+ background: var(--bg-2);
+ border-bottom: 1px solid var(--line);
+}
+
+.claim-inner {
+ max-width: var(--wide);
+ margin: 0 auto;
+ padding: 0.85rem 1.25rem;
+ display: grid;
+ gap: 0.35rem 2rem;
+}
+
+@media (min-width: 800px) {
+ .claim-inner {
+ grid-template-columns: 1fr 1fr;
+ }
+}
+
+.claim .k {
+ margin: 0;
+ color: var(--mute);
+ font-size: 0.72rem;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+}
+
+.claim p {
+ margin: 0.15rem 0 0;
+}
+
+.claim .meta {
+ grid-column: 1 / -1;
+ margin-top: 0.35rem;
+ color: var(--mute);
+ font-family: var(--mono);
+ font-size: 0.78rem;
+}
+
+.page {
+ max-width: var(--wide);
+ margin: 0 auto;
+ padding: 1.75rem 1.25rem 4rem;
+}
+
+h1,
+h2,
+h3 {
+ font-family: var(--serif);
+ font-weight: 600;
+ line-height: 1.2;
+}
+
+h1 {
+ font-size: clamp(2rem, 4vw, 3rem);
+ margin: 0 0 0.75rem;
+}
+
+h2 {
+ font-size: 1.45rem;
+ margin: 2.4rem 0 0.8rem;
+}
+
+.kicker {
+ margin: 0 0 0.5rem;
+ color: var(--green);
+ font-size: 0.78rem;
+ font-weight: 600;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+}
+
+.lede {
+ font-size: 1.2rem;
+ max-width: 40rem;
+ margin: 0 0 1rem;
+}
+
+.hold-line {
+ display: inline-block;
+ margin: 0 0 1rem;
+ padding: 0.2rem 0.55rem;
+ border: 1px solid var(--hold);
+ color: var(--hold);
+ font-weight: 600;
+ font-size: 0.88rem;
+}
+
+.hero {
+ display: grid;
+ gap: 1.5rem;
+}
+
+@media (min-width: 900px) {
+ .hero {
+ grid-template-columns: 1.3fr 0.9fr;
+ align-items: start;
+ }
+}
+
+.panel {
+ background: var(--bg-2);
+ border: 1px solid var(--line);
+ padding: 1.1rem 1.15rem;
+}
+
+.panel h2,
+.panel h3 {
+ margin-top: 0;
+}
+
+.panel h3 {
+ font-size: 1.15rem;
+}
+
+.stats {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 0.75rem;
+}
+
+.stat strong {
+ display: block;
+ font-family: var(--mono);
+ font-size: 1.4rem;
+}
+
+.stat span {
+ color: var(--mute);
+ font-size: 0.85rem;
+}
+
+.actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.6rem;
+ margin: 1.2rem 0 0;
+}
+
+.btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ text-decoration: none;
+ font-weight: 600;
+ padding: 0.55rem 0.95rem;
+ border-radius: 2px;
+}
+
+.btn-gold {
+ background: var(--gold);
+ color: var(--nav);
+}
+
+.btn-ghost {
+ border: 1px solid var(--ink);
+ color: var(--ink);
+}
+
+.why {
+ display: grid;
+ gap: 0;
+}
+
+.why article {
+ padding: 1rem 0;
+ border-top: 1px solid var(--line);
+}
+
+.why h3 {
+ margin: 0 0 0.35rem;
+ font-size: 1.15rem;
+}
+
+.pipeline {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+.pipeline li {
+ display: grid;
+ grid-template-columns: auto 1fr auto;
+ gap: 0.75rem;
+ align-items: center;
+ padding: 0.7rem 0;
+ border-top: 1px solid var(--line);
+}
+
+.journey {
+ list-style: none;
+ margin: 0.5rem 0 0;
+ padding: 0;
+ display: grid;
+ gap: 0.65rem;
+}
+
+@media (min-width: 720px) {
+ .journey {
+ grid-template-columns: repeat(2, 1fr);
+ }
+}
+
+@media (min-width: 1100px) {
+ .journey {
+ grid-template-columns: repeat(6, minmax(0, 1fr));
+ gap: 0.5rem;
+ }
+}
+
+.journey-card {
+ background: var(--bg-2);
+ border: 1px solid var(--line);
+ border-top: 3px solid var(--line);
+ padding: 0.85rem 0.8rem 0.95rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.4rem;
+ min-width: 0;
+}
+
+.journey-card.kind-evidence {
+ border-top-color: var(--ink);
+}
+
+.journey-card.kind-verification {
+ border-top-color: var(--green);
+}
+
+.journey-card.kind-inference {
+ border-top-color: var(--hold);
+}
+
+.journey-label {
+ margin: 0;
+ font-family: var(--sans);
+ font-size: 0.72rem;
+ font-weight: 700;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+}
+
+.journey-what,
+.journey-why,
+.journey-kind {
+ margin: 0;
+ font-size: 0.88rem;
+}
+
+.journey-kind {
+ color: var(--mute);
+ font-size: 0.78rem;
+}
+
+.journey-card details {
+ margin-top: auto;
+ font-size: 0.82rem;
+}
+
+.journey-steps {
+ margin: 0.4rem 0 0;
+ padding-left: 1.1rem;
+}
+
+.layers {
+ display: grid;
+ gap: 0.75rem;
+}
+
+@media (min-width: 800px) {
+ .layers {
+ grid-template-columns: repeat(3, 1fr);
+ }
+}
+
+.idx {
+ font-family: var(--mono);
+ color: var(--mute);
+ font-size: 0.8rem;
+}
+
+.step-title {
+ font-weight: 600;
+}
+
+.step-meta {
+ color: var(--mute);
+ font-size: 0.9rem;
+}
+
+.faq details {
+ border-top: 1px solid var(--line);
+ padding: 0.85rem 0;
+}
+
+.faq summary {
+ cursor: pointer;
+ font-weight: 600;
+ font-size: 1.05rem;
+}
+
+.faq details p {
+ margin: 0.55rem 0 0;
+ color: var(--mute);
+ max-width: 46rem;
+}
+
+.mark {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.35rem;
+ font-family: var(--mono);
+ font-size: 0.75rem;
+ letter-spacing: 0.04em;
+}
+
+.mark i {
+ width: 0.5rem;
+ height: 0.5rem;
+ border-radius: 50%;
+ background: currentColor;
+}
+
+.mark.pass {
+ color: var(--pass);
+}
+.mark.fail {
+ color: var(--fail);
+}
+.mark.hold {
+ color: var(--hold);
+}
+
+.filters {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.75rem;
+ margin: 0.5rem 0 1rem;
+}
+
+.filters label {
+ display: flex;
+ flex-direction: column;
+ gap: 0.2rem;
+ font-size: 0.78rem;
+ color: var(--mute);
+}
+
+select,
+textarea,
+button {
+ font: inherit;
+}
+
+select,
+textarea {
+ color: var(--ink);
+ background: var(--bg-2);
+ border: 1px solid var(--line);
+ padding: 0.35rem 0.45rem;
+}
+
+button,
+.btn-solid {
+ font: inherit;
+ border: 1px solid var(--ink);
+ background: var(--ink);
+ color: var(--bg);
+ padding: 0.4rem 0.75rem;
+ cursor: pointer;
+}
+
+button:disabled {
+ cursor: not-allowed;
+ background: transparent;
+ color: var(--mute);
+ border-color: var(--line);
+}
+
+.docket {
+ width: 100%;
+ border-collapse: collapse;
+ background: var(--bg-2);
+}
+
+.docket th,
+.docket td {
+ text-align: left;
+ vertical-align: top;
+ padding: 0.7rem 0.65rem;
+ border-bottom: 1px solid var(--line);
+ font-size: 0.95rem;
+}
+
+.docket th {
+ color: var(--mute);
+ font-size: 0.72rem;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+}
+
+.docket a.title {
+ color: var(--ink);
+ font-weight: 600;
+ text-decoration: none;
+}
+
+.docket a.title:hover {
+ color: var(--link);
+}
+
+.docket tr.superseded td {
+ color: var(--mute);
+ text-decoration: line-through;
+}
+
+.group-h {
+ margin: 1.6rem 0 0.4rem;
+ font-size: 1.05rem;
+}
+
+.cards {
+ display: grid;
+ gap: 1rem;
+}
+
+@media (min-width: 800px) {
+ .cards {
+ grid-template-columns: 1fr 1fr;
+ }
+}
+
+.card {
+ background: var(--bg-2);
+ border: 1px solid var(--line);
+ padding: 1.15rem;
+}
+
+.card h2 {
+ margin-top: 0;
+}
+
+.digest {
+ font-family: var(--mono);
+ font-size: 0.82rem;
+ overflow-wrap: anywhere;
+}
+
+.chain {
+ list-style: none;
+ margin: 0.5rem 0 1.2rem;
+ padding: 0;
+ border-left: 2px solid var(--line);
+}
+
+.chain li {
+ padding: 0.45rem 0 0.45rem 1rem;
+}
+
+.chain .gap {
+ min-height: 2.5rem;
+ border-left: 2px dashed var(--fail);
+ margin-left: -2px;
+ color: var(--fail);
+ font-style: italic;
+}
+
+.profile {
+ background: var(--bg-2);
+ border: 1px solid var(--line);
+ padding: 1rem 1.1rem;
+ margin: 0 0 0.75rem;
+}
+
+.profile h2 {
+ margin-top: 0;
+}
+
+.composer {
+ background: var(--bg-2);
+ border: 1px solid var(--line);
+ padding: 1rem;
+ opacity: 0.72;
+}
+
+.composer textarea {
+ width: 100%;
+ margin: 0.35rem 0 0.7rem;
+}
+
+footer.site {
+ background: var(--nav);
+ color: #c5cdc6;
+ padding: 2rem 0 2.5rem;
+ margin-top: 2rem;
+}
+
+footer.site a {
+ color: var(--gold);
+}
+
+footer.site .packs a {
+ margin-right: 0.7rem;
+}
+
+.error {
+ color: var(--fail);
+}
+
+@media (max-width: 720px) {
+ .docket,
+ .docket tbody,
+ .docket tr,
+ .docket td,
+ .docket th {
+ display: block;
+ width: 100%;
+ }
+ .docket thead {
+ display: none;
+ }
+ .docket td {
+ padding: 0.2rem 0;
+ border: 0;
+ }
+ .docket tr {
+ padding: 0.8rem 0.65rem;
+ border-bottom: 1px solid var(--line);
+ }
+ .pipeline li {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/frontend/src/styles/tokens.css b/frontend/src/styles/tokens.css
new file mode 100644
index 00000000..affa6aec
--- /dev/null
+++ b/frontend/src/styles/tokens.css
@@ -0,0 +1,20 @@
+:root {
+ --bg: #f6f4ee;
+ --bg-2: #fffdf8;
+ --ink: #141c18;
+ --mute: #5a645e;
+ --line: #ddd6c8;
+ --gold: #e0b422;
+ --green: #1f7a3a;
+ --pass: #1f7a3a;
+ --fail: #b42318;
+ --hold: #9a6b12;
+ --link: #1c5f8a;
+ --tape: #c2410c;
+ --nav: #121814;
+ --nav-ink: #f6f4ee;
+ --serif: 'Source Serif 4', Georgia, serif;
+ --sans: 'Source Sans 3', 'Segoe UI', sans-serif;
+ --mono: 'Source Code Pro', ui-monospace, monospace;
+ --wide: 72rem;
+}
diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts
new file mode 100644
index 00000000..11f02fe2
--- /dev/null
+++ b/frontend/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/frontend/tests/adapter.test.ts b/frontend/tests/adapter.test.ts
new file mode 100644
index 00000000..985ff7cf
--- /dev/null
+++ b/frontend/tests/adapter.test.ts
@@ -0,0 +1,38 @@
+import { describe, expect, it } from 'vitest';
+import { DisabledAdapter } from '../src/inference/DisabledAdapter';
+import { MockAdapter } from '../src/inference/MockAdapter';
+import { GenerationUnavailable, IdentityMismatch } from '../src/inference/types';
+
+describe('DisabledAdapter', () => {
+ it('refuses generation', async () => {
+ const adapter = new DisabledAdapter();
+ const caps = await adapter.capabilities();
+ expect(caps.available).toBe(false);
+ expect(caps.mock).toBe(false);
+ await expect(adapter.generate()).rejects.toBeInstanceOf(GenerationUnavailable);
+ });
+});
+
+describe('MockAdapter', () => {
+ it('rejects a mismatched release identity', async () => {
+ const adapter = new MockAdapter(true);
+ await expect(
+ adapter.generate({
+ releaseId: 'fixture-base',
+ mode: 'completion',
+ prompt: 'hi',
+ }),
+ ).rejects.toBeInstanceOf(IdentityMismatch);
+ });
+
+ it('labels mock output', async () => {
+ const adapter = new MockAdapter();
+ const response = await adapter.generate({
+ releaseId: 'fixture-base',
+ mode: 'completion',
+ prompt: 'hi',
+ });
+ expect(response.disclaimer).toMatch(/not generated by a released/i);
+ expect(response.releaseId).toBe('fixture-base');
+ });
+});
diff --git a/frontend/tests/journey.test.ts b/frontend/tests/journey.test.ts
new file mode 100644
index 00000000..3106c79b
--- /dev/null
+++ b/frontend/tests/journey.test.ts
@@ -0,0 +1,16 @@
+import { describe, expect, it } from 'vitest';
+import { worstResult } from '../src/content/copy';
+
+describe('worstResult', () => {
+ it('lets FAIL dominate', () => {
+ expect(worstResult(['PASS', 'FAIL', 'NOT_RUN'])).toBe('FAIL');
+ });
+
+ it('does not treat mixed PASS and NOT_RUN as a full pass', () => {
+ expect(worstResult(['PASS', 'NOT_RUN'])).toBe('NOT_RUN');
+ });
+
+ it('returns PASS only when every result is PASS', () => {
+ expect(worstResult(['PASS', 'PASS'])).toBe('PASS');
+ });
+});
diff --git a/frontend/tests/validate.test.ts b/frontend/tests/validate.test.ts
new file mode 100644
index 00000000..b087eca6
--- /dev/null
+++ b/frontend/tests/validate.test.ts
@@ -0,0 +1,69 @@
+import { describe, expect, it } from 'vitest';
+import { loadSnapshot } from '../src/data/adapters/snapshotAdapter';
+import { SnapshotError } from '../src/data/contracts';
+import { releaseIsDownloadable, validateSnapshot } from '../src/data/validate';
+import emptyChecks from '../src/data/fixtures/empty-checks.json';
+import publicSnapshot from '../src/data/fixtures/public-snapshot.json';
+
+describe('validateSnapshot', () => {
+ it('accepts the public unreleased snapshot', () => {
+ const snap = validateSnapshot(publicSnapshot);
+ expect(snap.mode).toBe('public-snapshot');
+ expect(snap.releases.every((item) => item.availability === 'not-released')).toBe(
+ true,
+ );
+ expect(snap.checks.some((item) => item.result === 'PASS')).toBe(true);
+ expect(snap.checks.find((item) => item.id === 'chk-g01')?.locallyRecomputed).toBe(
+ false,
+ );
+ });
+
+ it('refuses an empty check list', () => {
+ expect(() => validateSnapshot(emptyChecks)).toThrow(SnapshotError);
+ expect(() => validateSnapshot(emptyChecks)).toThrow(/no checks/);
+ });
+
+ it('does not treat ok as PASS', () => {
+ const raw = structuredClone(publicSnapshot) as Record;
+ const checks = raw.checks as Record[];
+ checks[0] = { ...checks[0], result: 'ok' };
+ expect(() => validateSnapshot(raw)).toThrow(/not a known check result/);
+ });
+
+ it('rejects PASS without evidence references', () => {
+ const raw = structuredClone(publicSnapshot) as Record;
+ const checks = raw.checks as Record[];
+ checks[0] = { ...checks[0], evidence: [] };
+ expect(() => validateSnapshot(raw)).toThrow(/missing evidence/);
+ });
+
+ it('rejects a javascript: locator', () => {
+ const raw = structuredClone(publicSnapshot) as Record;
+ raw.source = { ...(raw.source as object), url: 'javascript:alert(1)' };
+ expect(() => validateSnapshot(raw)).toThrow(/disallowed scheme/);
+ });
+});
+
+describe('loadSnapshot', () => {
+ it('loads missing-parent without pretending the parent exists', () => {
+ const snap = loadSnapshot('missing-parent');
+ const child = snap.evidence[0];
+ const ids = new Set(snap.evidence.map((item) => item.id));
+ expect(child.parentIds.some((id) => !ids.has(id))).toBe(true);
+ });
+
+ it('keeps superseded FAIL rows', () => {
+ const snap = loadSnapshot('superseded');
+ const failed = snap.evidence.find((item) => item.result === 'FAIL');
+ const passed = snap.evidence.find((item) => item.result === 'PASS');
+ expect(failed?.supersededBy).toBe(passed?.id);
+ expect(passed?.supersedes).toBe(failed?.id);
+ });
+});
+
+describe('release gating', () => {
+ it('does not treat unreleased cards as downloadable', () => {
+ const snap = loadSnapshot('public');
+ expect(snap.releases.every((item) => !releaseIsDownloadable(item))).toBe(true);
+ });
+});
diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json
new file mode 100644
index 00000000..3f6cbc69
--- /dev/null
+++ b/frontend/tsconfig.app.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "ES2022",
+ "useDefineForClassFields": true,
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "resolveJsonModule": true,
+ "jsx": "react-jsx",
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["src", "tests"]
+}
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
new file mode 100644
index 00000000..1ffef600
--- /dev/null
+++ b/frontend/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "files": [],
+ "references": [
+ { "path": "./tsconfig.app.json" },
+ { "path": "./tsconfig.node.json" }
+ ]
+}
diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json
new file mode 100644
index 00000000..953116cc
--- /dev/null
+++ b/frontend/tsconfig.node.json
@@ -0,0 +1,21 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "ES2023",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
new file mode 100644
index 00000000..94302c9c
--- /dev/null
+++ b/frontend/vite.config.ts
@@ -0,0 +1,11 @@
+import { defineConfig } from 'vitest/config';
+import react from '@vitejs/plugin-react';
+
+export default defineConfig({
+ base: './',
+ plugins: [react()],
+ test: {
+ environment: 'jsdom',
+ globals: false,
+ },
+});
From dab44e091828da7d88afde777e5018e9ea4b5170 Mon Sep 17 00:00:00 2001
From: panshulthinks
Date: Sun, 27 Sep 2026 14:29:42 +0530
Subject: [PATCH 2/7] fix(frontend): address CodeRabbit review feedback and
polish UI
CodeRabbit PR #122 review:
- docs: update Node version requirement to 20.19+ or 22.12+
- docs: label default snapshot as practice-run fixture
- fix(validate): enforce strict array validation with asArray() helper
- fix(a11y): skip link focus + preventDefault, tabIndex on main
- fix(journey): map pipeline steps with NOT_RUN fallback
- fix(releases): render anchor for downloadable releases, disabled button otherwise
- test: add rejects non-array collection fields test
UI polish:
- feat(overview): update CTA buttons and labels
- feat(overview): show record date and source revision
- feat(claimstrip): hyperlink source revision
- feat(copy): clarify G01/G02 status in FAQ
- style: token and base CSS refinements
- chore: add e2e paths to .gitignore
---
frontend/.gitignore | 4 ++
frontend/README.md | 33 ++++++------
frontend/index.html | 10 +---
frontend/package-lock.json | 46 +++++++++++++++++
frontend/package.json | 4 +-
frontend/src/content/copy.ts | 2 +-
.../src/data/fixtures/public-snapshot.json | 18 +++----
frontend/src/data/validate.ts | 37 ++++++++------
frontend/src/layout/AppShell.tsx | 33 +++++++-----
frontend/src/layout/ClaimStrip.tsx | 4 +-
frontend/src/pages/Journey.tsx | 50 ++++++++++++-------
frontend/src/pages/OverviewPage.tsx | 18 ++++---
frontend/src/pages/ReleasesPage.tsx | 17 +++++--
frontend/src/styles/base.css | 31 ++++++------
frontend/src/styles/tokens.css | 6 +--
frontend/tests/validate.test.ts | 6 +++
frontend/vite.config.ts | 1 +
17 files changed, 211 insertions(+), 109 deletions(-)
diff --git a/frontend/.gitignore b/frontend/.gitignore
index a4d699a9..3ca6847f 100644
--- a/frontend/.gitignore
+++ b/frontend/.gitignore
@@ -2,3 +2,7 @@ node_modules
dist
*.local
.DS_Store
+test-results
+playwright-report
+blob-report
+playwright/.cache
diff --git a/frontend/README.md b/frontend/README.md
index feb1ac08..694562d7 100644
--- a/frontend/README.md
+++ b/frontend/README.md
@@ -1,8 +1,8 @@
# OpenVerifiableLLM frontend
-Static evidence docket. It does **not** train, sign, or talk to a live model.
+Static public record. It does **not** train, sign, or chat.
-Requires Node 20+.
+Node 20.19+ or 22.12+.
```sh
cd frontend
@@ -11,36 +11,39 @@ npm run dev
npm run test
npm run typecheck
npm run build
+npx playwright install chromium # once
+npm run e2e
```
-Open http://localhost:5173 — HashRouter, so routes look like `/#/evidence`.
+Dev: http://localhost:5173 — HashRouter (`/#/evidence`).
## Snapshot packs
-The app never scrapes `goal_state.json`. It loads a frozen JSON pack:
+The app never scrapes `goal_state.json`. Default load is the public-shaped pack.
| `?snapshot=` | What it is |
| --- | --- |
-| *(default)* `public` | Honest project-shaped snapshot: G01/G02-like PASS, models **not released**, full replay NOT_RUN |
-| `missing-parent` | Fixture: child names a parent that is not in the docket |
-| `superseded` | Fixture: FAIL remains listed after a later PASS |
-| `empty` | Fixture: zero checks — shown as an error, not success |
+| *(default)* `public` | Project-shaped fixture (not an approved public snapshot): G01/G02-like PASS, models **not released**, full replay NOT_RUN |
+| `missing-parent` | Test: child names a missing parent |
+| `superseded` | Test: old FAIL stays after a later PASS |
+| `empty` | Test: zero checks — shown as an error |
-Example: `/#/?snapshot=missing-parent`
+Example: `/#/?snapshot=missing-parent`
+Test packs are not linked in the public footer.
-## Claims this UI will not make
+## This UI will not claim
- Models are available
-- Full training replay passed
-- `locallyRecomputed` is true (always false here)
-- Copyable CLI (withheld until a tested command exists)
+- G02 reconstruction, full replay, or independent audit passed
+- `locallyRecomputed` is true
+- A copyable CLI
- Chat answers come from a released OpenVerifiableLLM model
## Maintainer still needs to provide
-- Approved public snapshot + git revision
+- Approved public snapshot + git revision (M3)
- Tested verify commands
-- GitHub Pages `base` path / URL
+- GitHub Pages `base` path
- Inference backend after a real release
Do not edit `src/` (pipeline), keys, or `project/` from this app.
diff --git a/frontend/index.html b/frontend/index.html
index 2fd2e987..7415838b 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -3,16 +3,10 @@
- OpenVerifiableLLM — evidence docket
+ OpenVerifiableLLM
-
-
-
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 908b2845..feebd89c 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -13,6 +13,7 @@
"react-router-dom": "^7.8.2"
},
"devDependencies": {
+ "@playwright/test": "^1.63.0",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@types/react": "^19.1.12",
@@ -964,6 +965,22 @@
"node": "^22.20 || ^24.12 || >=25"
}
},
+ "node_modules/@playwright/test": {
+ "version": "1.63.0",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.63.0.tgz",
+ "integrity": "sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright": "1.63.0"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-rc.3",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz",
@@ -2334,6 +2351,35 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/playwright": {
+ "version": "1.63.0",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz",
+ "integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright-core": "1.63.0"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.63.0",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz",
+ "integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
"node_modules/postcss": {
"version": "8.5.28",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index b27bdde1..5897a49b 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -8,7 +8,8 @@
"build": "tsc -b && vite build",
"preview": "vite preview",
"typecheck": "tsc -b --noEmit",
- "test": "vitest run"
+ "test": "vitest run",
+ "e2e": "playwright test"
},
"dependencies": {
"react": "^19.1.1",
@@ -16,6 +17,7 @@
"react-router-dom": "^7.8.2"
},
"devDependencies": {
+ "@playwright/test": "^1.63.0",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@types/react": "^19.1.12",
diff --git a/frontend/src/content/copy.ts b/frontend/src/content/copy.ts
index da492e08..7e754bee 100644
--- a/frontend/src/content/copy.ts
+++ b/frontend/src/content/copy.ts
@@ -135,7 +135,7 @@ export const FAQ = [
},
{
q: 'What can I do on this website?',
- a: 'Read the saved public record and follow links to original files. The Verify page explains what each kind of check would mean. This site does not train, sign, or generate text.',
+ a: 'Read the saved public record and follow links to original files. Listing Wikipedia files (G01) is marked as a practice-run pass. Reconstruction (G02) and later stages remain pending. This site does not train, sign, or generate text.',
},
{
q: 'What is a practice run versus the real run?',
diff --git a/frontend/src/data/fixtures/public-snapshot.json b/frontend/src/data/fixtures/public-snapshot.json
index 04991830..102944f4 100644
--- a/frontend/src/data/fixtures/public-snapshot.json
+++ b/frontend/src/data/fixtures/public-snapshot.json
@@ -11,7 +11,7 @@
"summary": {
"headline": "OpenVerifiableLLM keeps a public record of a small language model’s data, training, checkpoints, and released files so other people can inspect and later reproduce them. The models are not released yet.",
"mayClaim": [
- "The project team listed and downloaded the Wikipedia files for a practice run (G01–G02)"
+ "The project team listed the Wikipedia files for a practice run (G01)"
],
"mayNotClaim": [
"that a model is available to download or chat with",
@@ -19,7 +19,7 @@
"that someone outside the team has independently rebuilt it",
"that generated answers are true"
],
- "workflow": "The team has listed and fetched the Wikipedia files for a practice run (G01–G02). Training, a full re-run, and both models are not done yet."
+ "workflow": "G01 (listing Wikipedia files) is recorded as passed on a practice run. G02–G10 and both models remain pending."
},
"pipeline": [
{
@@ -34,7 +34,7 @@
"id": "wiki-acquire",
"title": "Download those Wikipedia files",
"phase": "source",
- "result": "PASS",
+ "result": "NOT_RUN",
"scope": "pilot",
"checkId": "chk-g02"
},
@@ -144,10 +144,10 @@
"profile": "data-reconstruction",
"phase": "source",
"scope": "pilot",
- "result": "PASS",
- "explanation": "The team says the Wikipedia files used in the practice run are stored in a public archive. Someone outside the team has not yet rebuilt that from scratch.",
- "performedBy": "project-operator",
- "attestedBy": "project-operator",
+ "result": "NOT_RUN",
+ "explanation": "The public evidence dataset still lists G02 as pending. This site does not treat dump reconstruction as passed.",
+ "performedBy": null,
+ "attestedBy": null,
"locallyRecomputed": false,
"evidence": [
{
@@ -207,11 +207,11 @@
},
{
"id": "ev-pr-101",
- "title": "Training-pipeline work on GitHub (pull request 101)",
+ "title": "Training-pipeline work on GitHub (pull request 101, merged)",
"kind": "report",
"phase": "source",
"scope": "pilot",
- "result": "PASS",
+ "result": "NOT_RUN",
"checkId": "chk-g02",
"source": {
"url": "https://github.com/AOSSIE-Org/OpenVerifiableLLM/pull/101",
diff --git a/frontend/src/data/validate.ts b/frontend/src/data/validate.ts
index abf9fb3e..95a43925 100644
--- a/frontend/src/data/validate.ts
+++ b/frontend/src/data/validate.ts
@@ -65,6 +65,13 @@ function asNumberOrNull(value: unknown, field: string): number | null {
return value;
}
+function asArray(value: unknown, field: string): unknown[] {
+ if (!Array.isArray(value)) {
+ throw new SnapshotError(`${field} must be an array`);
+ }
+ return value;
+}
+
function asStringList(value: unknown, field: string): string[] {
if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {
throw new SnapshotError(`${field} must be a list of strings`);
@@ -239,21 +246,21 @@ export function validateSnapshot(raw: unknown): Snapshot {
);
}
- const checks = Array.isArray(raw.checks)
- ? raw.checks.map((item, i) => parseCheck(item, i))
- : [];
- const evidence = Array.isArray(raw.evidence)
- ? raw.evidence.map((item, i) => parseEvidence(item, i))
- : [];
- const releases = Array.isArray(raw.releases)
- ? raw.releases.map((item, i) => parseRelease(item, i))
- : [];
- const pipeline = Array.isArray(raw.pipeline)
- ? raw.pipeline.map((item, i) => parsePipeline(item, i))
- : [];
- const commands = Array.isArray(raw.commands)
- ? raw.commands.map((item, i) => parseCommand(item, i))
- : [];
+ const checks = asArray(raw.checks, 'checks').map((item, i) =>
+ parseCheck(item, i),
+ );
+ const evidence = asArray(raw.evidence, 'evidence').map((item, i) =>
+ parseEvidence(item, i),
+ );
+ const releases = asArray(raw.releases, 'releases').map((item, i) =>
+ parseRelease(item, i),
+ );
+ const pipeline = asArray(raw.pipeline, 'pipeline').map((item, i) =>
+ parsePipeline(item, i),
+ );
+ const commands = asArray(raw.commands, 'commands').map((item, i) =>
+ parseCommand(item, i),
+ );
if (checks.length === 0) {
throw new SnapshotError(
diff --git a/frontend/src/layout/AppShell.tsx b/frontend/src/layout/AppShell.tsx
index 7d5918a2..dc3e82a5 100644
--- a/frontend/src/layout/AppShell.tsx
+++ b/frontend/src/layout/AppShell.tsx
@@ -18,7 +18,14 @@ export function AppShell() {
return (
<>
-
+ {
+ event.preventDefault();
+ document.getElementById('main')?.focus();
+ }}
+ >
Skip to content
@@ -43,7 +50,7 @@ export function AppShell() {
{snapshot ? : null}
-
+
{error ? (
Data error
@@ -64,15 +71,17 @@ export function AppShell() {
OpenVerifiableLLM is an AOSSIE project. This website shows a saved
public record. It does not train models, sign files, or chat.
-
- Snapshot packs:{' '}
- {SNAPSHOT_IDS.map((item) => (
-
- {item}
- {item === id ? ' (current)' : ''}
-
- ))}
-
+ {snapshot?.mode === 'fixture' ? (
+
+ Test packs:{' '}
+ {SNAPSHOT_IDS.map((item) => (
+
+ {item}
+ {item === id ? ' (current)' : ''}
+
+ ))}
+
+ ) : null}
GitHub
{' · '}
@@ -81,8 +90,6 @@ export function AppShell() {
{' · '}
aossie.org
- {' · '}
- Discord
diff --git a/frontend/src/layout/ClaimStrip.tsx b/frontend/src/layout/ClaimStrip.tsx
index 7ab40233..2e3c29d5 100644
--- a/frontend/src/layout/ClaimStrip.tsx
+++ b/frontend/src/layout/ClaimStrip.tsx
@@ -18,7 +18,9 @@ export function ClaimStrip({ snapshot }: { snapshot: Snapshot }) {
{snapshot.summary.mayNotClaim.join(' · ')}
- Saved {snapshot.generatedAt} · {revision} · this website did not re-run the checks
+ Saved {snapshot.generatedAt} ·{' '}
+ {revision.slice(0, 7)}
+ {' · '}this website did not re-run the checks
diff --git a/frontend/src/pages/Journey.tsx b/frontend/src/pages/Journey.tsx
index f232817c..628b544a 100644
--- a/frontend/src/pages/Journey.tsx
+++ b/frontend/src/pages/Journey.tsx
@@ -1,11 +1,12 @@
+import { useState } from 'react';
import { JOURNEY, SCOPE_LABEL, worstResult } from '../content/copy';
import type { CheckResult, PipelineStep, Snapshot } from '../data/contracts';
import { StatusMark } from '../layout/StatusMark';
function stageResult(snapshot: Snapshot, pipelineIds: string[], checkProfile: string | null): CheckResult {
- const fromSteps = snapshot.pipeline
- .filter((step) => pipelineIds.includes(step.id))
- .map((step) => step.result);
+ const fromSteps = pipelineIds.map(
+ (id) => snapshot.pipeline.find((step) => step.id === id)?.result ?? 'NOT_RUN',
+ );
const fromCheck = checkProfile
? snapshot.checks.filter((c) => c.profile === checkProfile).map((c) => c.result)
: [];
@@ -25,33 +26,44 @@ const KIND_LINE = {
} as const;
export function Journey({ snapshot }: { snapshot: Snapshot }) {
+ const [openId, setOpenId] = useState(null);
+
return (
{JOURNEY.map((stage) => {
const result = stageResult(snapshot, stage.pipelineIds, stage.checkProfile);
const steps = stepsFor(snapshot, stage.pipelineIds);
+ const isOpen = openId === stage.id;
return (
{stage.label}
{stage.what}
-
- Details
- {stage.why}
- {KIND_LINE[stage.kind]}
- {steps.length === 0 && stage.checkProfile ? (
-
- Check: {stage.checkProfile}
-
+
+ {
+ event.preventDefault();
+ setOpenId((current) => (current === stage.id ? null : stage.id));
+ }}
+ >
+ {isOpen ? 'Hide steps' : 'Steps'}
+
+ {isOpen ? (
+ <>
+ {stage.why}
+ {KIND_LINE[stage.kind]}
+ {steps.length === 0 && stage.checkProfile ? (
+ Check: {stage.checkProfile}
+ ) : null}
+
+ {steps.map((step) => (
+
+ {step.title} — · {SCOPE_LABEL[step.scope]}
+
+ ))}
+
+ >
) : null}
-
- {steps.map((step) => (
-
- {step.title} — · {SCOPE_LABEL[step.scope]} ·{' '}
- {step.id}
-
- ))}
-
);
diff --git a/frontend/src/pages/OverviewPage.tsx b/frontend/src/pages/OverviewPage.tsx
index 7b581758..00610265 100644
--- a/frontend/src/pages/OverviewPage.tsx
+++ b/frontend/src/pages/OverviewPage.tsx
@@ -23,14 +23,14 @@ export function OverviewPage() {
reproducible. This site shows the record; it does not train or chat.
-
- Evidence
+
+ Explore evidence
-
- Verification
+
+ View releases
-
- Inference
+
+ How verification works
@@ -55,6 +55,12 @@ export function OverviewPage() {
releases
+
+ Record {snapshot.generatedAt} ·{' '}
+
+ {(snapshot.source.revision ?? 'source').slice(0, 7)}
+
+
diff --git a/frontend/src/pages/ReleasesPage.tsx b/frontend/src/pages/ReleasesPage.tsx
index dbdaa518..b9019d0e 100644
--- a/frontend/src/pages/ReleasesPage.tsx
+++ b/frontend/src/pages/ReleasesPage.tsx
@@ -47,9 +47,20 @@ export function ReleasesPage() {
-
- Download
- {' '}
+ {ready && release.repository ? (
+
+ Download
+
+ ) : (
+
+ Download
+
+ )}{' '}
Generate
diff --git a/frontend/src/styles/base.css b/frontend/src/styles/base.css
index fba71002..b7a678f2 100644
--- a/frontend/src/styles/base.css
+++ b/frontend/src/styles/base.css
@@ -118,15 +118,16 @@ summary:focus-visible {
.mast nav a {
color: #d7ddd8;
text-decoration: none;
- padding: 0.4rem 0.7rem;
- border-radius: 999px;
- font-size: 0.92rem;
+ padding: 0.35rem 0;
+ margin: 0 0.65rem;
+ border-bottom: 1px solid transparent;
+ font-size: 0.95rem;
}
.mast nav a:hover,
.mast nav a[aria-current='page'] {
- color: var(--nav);
- background: var(--gold);
+ color: var(--nav-ink);
+ border-bottom-color: var(--gold);
}
.tape {
@@ -205,11 +206,9 @@ h2 {
.kicker {
margin: 0 0 0.5rem;
- color: var(--green);
- font-size: 0.78rem;
+ color: var(--mute);
+ font-size: 0.8rem;
font-weight: 600;
- letter-spacing: 0.12em;
- text-transform: uppercase;
}
.lede {
@@ -289,9 +288,10 @@ h2 {
border-radius: 2px;
}
-.btn-gold {
- background: var(--gold);
- color: var(--nav);
+.btn-solid {
+ background: var(--ink);
+ color: var(--bg);
+ border: 1px solid var(--ink);
}
.btn-ghost {
@@ -335,6 +335,7 @@ h2 {
padding: 0;
display: grid;
gap: 0.65rem;
+ align-items: start;
}
@media (min-width: 720px) {
@@ -347,6 +348,7 @@ h2 {
.journey {
grid-template-columns: repeat(6, minmax(0, 1fr));
gap: 0.5rem;
+ align-items: start;
}
}
@@ -359,6 +361,7 @@ h2 {
flex-direction: column;
gap: 0.4rem;
min-width: 0;
+ align-self: start;
}
.journey-card.kind-evidence {
@@ -376,10 +379,8 @@ h2 {
.journey-label {
margin: 0;
font-family: var(--sans);
- font-size: 0.72rem;
+ font-size: 0.78rem;
font-weight: 700;
- letter-spacing: 0.12em;
- text-transform: uppercase;
}
.journey-what,
diff --git a/frontend/src/styles/tokens.css b/frontend/src/styles/tokens.css
index affa6aec..004a9a10 100644
--- a/frontend/src/styles/tokens.css
+++ b/frontend/src/styles/tokens.css
@@ -13,8 +13,8 @@
--tape: #c2410c;
--nav: #121814;
--nav-ink: #f6f4ee;
- --serif: 'Source Serif 4', Georgia, serif;
- --sans: 'Source Sans 3', 'Segoe UI', sans-serif;
- --mono: 'Source Code Pro', ui-monospace, monospace;
+ --serif: Georgia, 'Times New Roman', serif;
+ --sans: ui-sans-serif, system-ui, 'Segoe UI', sans-serif;
+ --mono: ui-monospace, 'Cascadia Code', 'Consolas', monospace;
--wide: 72rem;
}
diff --git a/frontend/tests/validate.test.ts b/frontend/tests/validate.test.ts
index b087eca6..74ee0777 100644
--- a/frontend/tests/validate.test.ts
+++ b/frontend/tests/validate.test.ts
@@ -42,6 +42,12 @@ describe('validateSnapshot', () => {
raw.source = { ...(raw.source as object), url: 'javascript:alert(1)' };
expect(() => validateSnapshot(raw)).toThrow(/disallowed scheme/);
});
+
+ it('rejects non-array collection fields', () => {
+ const raw = structuredClone(publicSnapshot) as Record;
+ raw.evidence = 'invalid';
+ expect(() => validateSnapshot(raw)).toThrow(/evidence must be an array/);
+ });
});
describe('loadSnapshot', () => {
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
index 94302c9c..e3c60235 100644
--- a/frontend/vite.config.ts
+++ b/frontend/vite.config.ts
@@ -7,5 +7,6 @@ export default defineConfig({
test: {
environment: 'jsdom',
globals: false,
+ exclude: ['e2e/**', 'node_modules/**'],
},
});
From d13c5fc00ced744a432bda1a3ca8f3d6dfd7030f Mon Sep 17 00:00:00 2001
From: panshulthinks
Date: Sun, 27 Sep 2026 13:59:42 +0530
Subject: [PATCH 3/7] fix(frontend): mark bundled status as draft fixture data
---
frontend/src/data/fixtures/public-snapshot.json | 2 +-
frontend/src/layout/AppShell.tsx | 6 +++++-
frontend/tests/validate.test.ts | 4 ++--
3 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/frontend/src/data/fixtures/public-snapshot.json b/frontend/src/data/fixtures/public-snapshot.json
index 102944f4..f6a05e01 100644
--- a/frontend/src/data/fixtures/public-snapshot.json
+++ b/frontend/src/data/fixtures/public-snapshot.json
@@ -1,7 +1,7 @@
{
"schemaVersion": "ovllm.frontend.snapshot.v1",
"generatedAt": "2026-09-19T12:00:00Z",
- "mode": "public-snapshot",
+ "mode": "fixture",
"source": {
"url": "https://github.com/AOSSIE-Org/OpenVerifiableLLM",
"revision": "03551ddedf9e54ae4355cf7e3f85c83c27e5f971",
diff --git a/frontend/src/layout/AppShell.tsx b/frontend/src/layout/AppShell.tsx
index dc3e82a5..5e8a2074 100644
--- a/frontend/src/layout/AppShell.tsx
+++ b/frontend/src/layout/AppShell.tsx
@@ -45,7 +45,11 @@ export function AppShell() {
{snapshot?.mode === 'fixture' ? (
- Test data — not the real project status
+
+ {id === 'public'
+ ? 'Draft status — waiting for maintainer approval'
+ : 'Test data — not the real project status'}
+
) : null}
{snapshot ? : null}
diff --git a/frontend/tests/validate.test.ts b/frontend/tests/validate.test.ts
index 74ee0777..c8ed4df5 100644
--- a/frontend/tests/validate.test.ts
+++ b/frontend/tests/validate.test.ts
@@ -6,9 +6,9 @@ import emptyChecks from '../src/data/fixtures/empty-checks.json';
import publicSnapshot from '../src/data/fixtures/public-snapshot.json';
describe('validateSnapshot', () => {
- it('accepts the public unreleased snapshot', () => {
+ it('accepts the unreleased draft snapshot as fixture data', () => {
const snap = validateSnapshot(publicSnapshot);
- expect(snap.mode).toBe('public-snapshot');
+ expect(snap.mode).toBe('fixture');
expect(snap.releases.every((item) => item.availability === 'not-released')).toBe(
true,
);
From d0ff735f694f7839bd28d5dabd52d1b3db4bd41f Mon Sep 17 00:00:00 2001
From: panshulthinks
Date: Sun, 27 Sep 2026 14:00:44 +0530
Subject: [PATCH 4/7] fix(frontend): label overview status counts as checks
---
frontend/src/pages/OverviewPage.tsx | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/frontend/src/pages/OverviewPage.tsx b/frontend/src/pages/OverviewPage.tsx
index 00610265..908c8912 100644
--- a/frontend/src/pages/OverviewPage.tsx
+++ b/frontend/src/pages/OverviewPage.tsx
@@ -40,15 +40,15 @@ export function OverviewPage() {
{pass}
- passed
+ checks passed
{fail}
- failed
+ checks failed
{pending}
- pending
+ checks pending
{released}
@@ -61,6 +61,7 @@ export function OverviewPage() {
{(snapshot.source.revision ?? 'source').slice(0, 7)}
+
Check counts are separate from the pipeline stages below.
From 2d2e78c23f1996890567f38e670c0ce0dab099f9 Mon Sep 17 00:00:00 2001
From: panshulthinks
Date: Sun, 27 Sep 2026 14:01:16 +0530
Subject: [PATCH 5/7] docs(frontend): mark mock inference as test-only until M4
---
frontend/ARCHITECTURE.md | 9 +++++----
frontend/DESIGN.md | 5 +++--
2 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/frontend/ARCHITECTURE.md b/frontend/ARCHITECTURE.md
index fd87b596..44cf82ab 100644
--- a/frontend/ARCHITECTURE.md
+++ b/frontend/ARCHITECTURE.md
@@ -72,7 +72,7 @@ We only *display* what the maintainer allows.
│ │
│ InferenceGateway │
│ public build → DisabledAdapter │
-│ ?fixture=1 → MockAdapter + sticky warning │
+│ tests only → MockAdapter (UI preview planned for M4) │
└─────────────────────────────────────────────────────────────┘
```
@@ -184,7 +184,7 @@ Failed public fetch: `ErrorPage`. No silent fixture.
| §5.2 Detail | `EvidenceDetailPage` | Digest copy, parents/children, missing parent as gap, superseded kept, bounded escaped JSON. |
| §5.3 Releases | `ReleasesPage` | Two cards. Disabled download/generate until metadata. No invented HF names. |
| §5.4 Verify | `VerifyPage` | Five profiles, distinct meanings. CLI = “Instructions pending release” until ryoari pastes a tested command. |
-| §5.5 Try | `TryPage` + adapters | Public: generation unavailable. Fixture: labelled mock. Reject identity mismatch. No logging. |
+| §5.5 Try | `TryPage` + adapters | Public: generation unavailable. Mock adapter is test-only until M4. Reject identity mismatch. No logging. |
| §6 Status | `StatusMark` | PASS/FAIL/NOT_RUN/UNAVAILABLE/UNSUPPORTED + text, not color-only. Empty checks ≠ success. |
| §8 Fixtures | `src/data/fixtures` | All listed cases exist as JSON; tests load them. |
| §9 a11y | CSS + tests | Keyboard, 200% zoom, reduced motion, contrast, announced copy. |
@@ -206,8 +206,9 @@ interface InferenceAdapter {
- `MockAdapter`: capabilities.mock = true. Every response includes
`disclaimer: 'Example UI response — not generated by a released OpenVerifiableLLM model'`.
If `response.releaseId !== request.releaseId` → reject.
-- Production bundle must not import a path that enables mock by default.
- Gate with `import.meta.env.VITE_INFERENCE=mock` **and** a visible UI flag.
+- Nothing in the app selects `MockAdapter` yet. It is only used in
+ `tests/adapter.test.ts`. A build flag for a labelled mock preview is
+ planned for milestone 4.
No Ollama in v1. Connecting Ollama to unverified weights is how we
accidentally ship the product the brief forbids.
diff --git a/frontend/DESIGN.md b/frontend/DESIGN.md
index 6474ac80..e1f609a3 100644
--- a/frontend/DESIGN.md
+++ b/frontend/DESIGN.md
@@ -134,7 +134,7 @@ Five routes. Nothing else in v1.
| `/evidence/:id` | One object: digest, parents, children, original link |
| `/releases` | Base card + chat card, both gated until real metadata |
| `/verify` | Five verification *profiles*, not a magic button |
-| `/try` | Inference shell. Public: disabled. Fixture preview: labelled mock |
+| `/try` | Inference shell. Public: disabled. Mock preview planned for M4 |
Nav: Overview · Evidence · Releases · Verify · Try
Footer: AOSSIE, GitHub, Hugging Face evidence dataset, snapshot time, “this is
@@ -271,7 +271,8 @@ Public build:
- Banner: **Generation is unavailable until a verified model release is connected**
- No silent fallback to GPT / Gemini / “any Ollama model”
-Fixture / preview build (`?fixture=1` or a documented env flag):
+Mock preview (planned for milestone 4, not wired up yet; `MockAdapter` is
+only used in tests today):
- Mock adapter only
- Every response carries a visible, sticky label:
From a6ad67594983deb8400acaee916566be2a94a808 Mon Sep 17 00:00:00 2001
From: panshulthinks
Date: Sun, 27 Sep 2026 14:02:19 +0530
Subject: [PATCH 6/7] docs(frontend): replace design and architecture notes
with a concise README
---
frontend/ARCHITECTURE.md | 292 ----------------------------
frontend/DESIGN.md | 406 ---------------------------------------
frontend/README.md | 89 ++++++---
3 files changed, 58 insertions(+), 729 deletions(-)
delete mode 100644 frontend/ARCHITECTURE.md
delete mode 100644 frontend/DESIGN.md
diff --git a/frontend/ARCHITECTURE.md b/frontend/ARCHITECTURE.md
deleted file mode 100644
index 44cf82ab..00000000
--- a/frontend/ARCHITECTURE.md
+++ /dev/null
@@ -1,292 +0,0 @@
-# Frontend architecture
-
-Maps `FRONTEND_CONTRIBUTOR_BRIEF.md` onto a static app we can merge.
-Visual language lives in `DESIGN.md`. This file is structure, data, and
-what “crazy” is allowed to mean.
-
----
-
-## 1. Research: how AOSSIE actually ships frontends
-
-What mentors already reward in this org (GSoC 2025–2026):
-
-| Project | What it is | Frontend pattern | Lesson for us |
-| --- | --- | --- | --- |
-| **OrgExplorer** | GitHub org analytics | Vite + React + TS + Tailwind, **no backend**, GitHub Pages, IndexedDB cache | Sunny/less-cloud. Browser is the runtime. |
-| **PictoPy** | Desktop gallery | React + Vite + Tailwind + ShadCN *inside Tauri*; separate static landing on `pictopy.aossie.org` | App UI ≠ marketing site. Landing is static Pages. |
-| **PictoPy-Website / Resonate-Website / DebateAI-LandingPage** | Project landings | Vite/React, GH Pages, `*.aossie.org` | Org wants static landings, not Vercel. |
-| **AOSSIE Website** | Org home | Next.js 16 + Tailwind v4 | Exception: the org site. **Our brief forbids a backend.** Do not copy Next. |
-| **Devr.AI** | DevRel agent | React dashboard + FastAPI | Cloud-heavy. Opposite of this assignment. |
-| **LandingPages idea (GSoC 2026)** | Bruno/M4dhav | Static pages, SEO, **better than generic AI landings** | Mentors have said out loud they can smell AI templates. |
-| **UI idea** | Same mentors | Clean, minimal, **easy to maintain next year** | No throwaway Aceternity theme. |
-
-Shared stack we should match: **React + TypeScript + Vite + static host**.
-Shared philosophy: no paid APIs, no login required, GitHub Pages.
-
-What they do **not** have: a site whose job is *refusing to overclaim*.
-That is our opening. OrgExplorer graphs GitHub. PictoPy shows photos.
-We show **a chain of checks that can be broken**. If we do that with
-restraint, it is the most distinctive frontend in the org this year.
-
----
-
-## 2. Product, in one sentence
-
-A **static evidence docket**: public snapshot in, honest pages out.
-It never trains, never signs, never calls GPU, never pretends a model exists.
-
-```
-approved JSON snapshot → validate → view models → pages
- ↑ ↓
- fixtures (dev) fail closed (no mock fallback)
-```
-
-The training pipeline (`src/`, PR #101, RunPod) is a different program.
-We only *display* what the maintainer allows.
-
----
-
-## 3. Runtime architecture
-
-```
-┌─────────────────────────────────────────────────────────────┐
-│ Browser (GitHub Pages / local Vite) │
-│ │
-│ HashRouter ─ no server rewrites needed │
-│ │
-│ Layout │
-│ nav · ClaimStrip · · footer │
-│ │
-│ SnapshotProvider │
-│ mode: fixture | public-snapshot │
-│ load → parse → validate → freeze │
-│ on error: ErrorView (never swap in a happy fixture) │
-│ │
-│ Pages │
-│ / Overview │
-│ /evidence list + filters │
-│ /evidence/:id detail + chain rail │
-│ /releases base + chat cards │
-│ /verify five profiles │
-│ /try inference shell │
-│ │
-│ InferenceGateway │
-│ public build → DisabledAdapter │
-│ tests only → MockAdapter (UI preview planned for M4) │
-└─────────────────────────────────────────────────────────────┘
-```
-
-No React Query polling. No IndexedDB of visitor chats. OrgExplorer uses
-IndexedDB because it *fetches GitHub*. We ship the snapshot **in the
-build** (`public/data/`). Optional later: fetch one allowlisted URL with
-integrity, still fail-closed.
-
-**HashRouter** is the static-safe strategy the brief asks for. Direct
-load of `/#/evidence/xyz` works on GitHub Pages without a 404.html hack.
-
----
-
-## 4. Directory (implement this, not a larger tree)
-
-```
-frontend/
- README.md
- DESIGN.md
- ARCHITECTURE.md
- package.json
- package-lock.json
- index.html
- vite.config.ts # base: './' until maintainer sets Pages path
- tsconfig.json
- src/
- main.tsx
- App.tsx # providers + routes
- layout/
- AppShell.tsx
- ClaimStrip.tsx
- StatusMark.tsx
- pages/
- OverviewPage.tsx
- EvidenceListPage.tsx
- EvidenceDetailPage.tsx
- ReleasesPage.tsx
- VerifyPage.tsx
- TryPage.tsx
- ErrorPage.tsx
- data/
- contracts.ts # types from brief §7.2
- validate.ts # runtime checks, fail closed
- adapters/
- snapshotAdapter.ts
- fixtures/ # labelled synthetic JSON (imported by tests too)
- inference/
- types.ts
- DisabledAdapter.ts
- MockAdapter.ts
- styles/
- tokens.css
- base.css
- public/
- data/
- public-snapshot.json # mode labelled; may be partial
- README.md # how to refresh + revision
- tests/
- validate.test.ts
- status.test.ts
- releases.test.ts
- adapter.test.ts
- navigation.test.ts # small browser test for unreleased flow
-```
-
-Do not add Tailwind/ShadCN/Aceternity unless a later PR needs it.
-Ordinary CSS + the token table in `DESIGN.md` is enough and is closer
-to “maintainable next year” than a UI kit.
-
----
-
-## 5. Data contract (brief §7, implemented)
-
-One versioned snapshot is the only store.
-
-```ts
-type CheckResult = 'PASS' | 'FAIL' | 'NOT_RUN' | 'UNAVAILABLE' | 'UNSUPPORTED';
-type EvidenceScope = 'fixture' | 'pilot' | 'production';
-type DataMode = 'fixture' | 'public-snapshot';
-type ReleaseAvailability = 'not-released' | 'available' | 'withdrawn';
-```
-
-`validate.ts` must:
-
-- reject unknown schema versions as **data error**, not PASS
-- map only known legacy aliases (`passed` → PASS, `pending` → NOT_RUN)
-- never coerce `"ok"` / `"complete"` / `"true"` to PASS
-- require: snapshot mode, generatedAt, source refs, checks[], releases[]
-- require each PASS to have profile, scope, explanation, evidence refs
-- mark missing mandatory fields UNAVAILABLE or throw `SnapshotError`
-
-Adapters keep `url`, `revision`, `path`, `sha256` as they came.
-Hashes are displayed as the source string. We do **not** JSON.parse and
-re-stringify then call that “the same bytes.”
-
-`locallyRecomputed` is **always false** in this static app. Showing a
-publisher JSON is not a local replay.
-
-Failed public fetch: `ErrorPage`. No silent fixture.
-
----
-
-## 6. Pages ↔ brief (everything must land)
-
-| Brief | Module | Behaviour that cannot be skipped |
-| --- | --- | --- |
-| §5.1 Overview | `OverviewPage` | Purpose, **not released**, 3 actions, vertical pipeline with real statuses, provenance ≠ truth. No % complete, no fake date, no hero chat. |
-| §5.2 Evidence | `EvidenceListPage` | Table/list primary. Filters: phase, kind, result, scope. Size + link, never auto-download dumps. |
-| §5.2 Detail | `EvidenceDetailPage` | Digest copy, parents/children, missing parent as gap, superseded kept, bounded escaped JSON. |
-| §5.3 Releases | `ReleasesPage` | Two cards. Disabled download/generate until metadata. No invented HF names. |
-| §5.4 Verify | `VerifyPage` | Five profiles, distinct meanings. CLI = “Instructions pending release” until ryoari pastes a tested command. |
-| §5.5 Try | `TryPage` + adapters | Public: generation unavailable. Mock adapter is test-only until M4. Reject identity mismatch. No logging. |
-| §6 Status | `StatusMark` | PASS/FAIL/NOT_RUN/UNAVAILABLE/UNSUPPORTED + text, not color-only. Empty checks ≠ success. |
-| §8 Fixtures | `src/data/fixtures` | All listed cases exist as JSON; tests load them. |
-| §9 a11y | CSS + tests | Keyboard, 200% zoom, reduced motion, contrast, announced copy. |
-| §11 checklist | PR template in README | Tick with screenshots, not vibes. |
-
----
-
-## 7. Inference boundary (brief §7.3)
-
-```ts
-interface InferenceAdapter {
- capabilities(): Promise;
- generate(request: GenerationRequest, signal?: AbortSignal): Promise;
-}
-```
-
-- `DisabledAdapter`: capabilities.available = false. `generate` throws
- `GenerationUnavailable`.
-- `MockAdapter`: capabilities.mock = true. Every response includes
- `disclaimer: 'Example UI response — not generated by a released OpenVerifiableLLM model'`.
- If `response.releaseId !== request.releaseId` → reject.
-- Nothing in the app selects `MockAdapter` yet. It is only used in
- `tests/adapter.test.ts`. A build flag for a labelled mock preview is
- planned for milestone 4.
-
-No Ollama in v1. Connecting Ollama to unverified weights is how we
-accidentally ship the product the brief forbids.
-
----
-
-## 8. What can be “crazy” (attention) vs what cannot
-
-Mentors (Bruno, Archit, ryoari) punish fake green and template landings.
-They notice **one true interaction** that the rest of AOSSIE does not have.
-
-### Build these — they are distinctive and on-brief
-
-1. **Claim strip (signature)**
- Every route states what the page may and may not claim, plus snapshot
- time and source commit. Screenshot this in the PR.
-
-2. **Broken chain rail**
- Parent → this → children. Missing parent is a physical gap. This is
- the G09 “publication must not overclaim” idea as UI.
-
-3. **Fail-closed empty docket**
- A designed screen for “zero checks” and “malformed snapshot.” Most
- dashboards fake a happy empty state. Ours must look unfinished.
-
-4. **Supersession timeline**
- FAIL report remains, struck, with an arrow to the later PASS. History
- is not rewritten. Unique vs OrgExplorer’s live graphs.
-
-5. **Narrow browser checksum (optional M3+)**
- User drops a *small* published file; Web Crypto SHA-256 compared to
- the snapshot digest. Label: **checksum of this file in this browser,
- not training replay, not publisher trust.** Fits “browser checksum
- must be labelled as that narrow check.”
-
-6. **Fixture / public mode switch that is ugly on purpose**
- A persistent “FIXTURE” tape on the viewport when not in public mode.
- Impossible to screenshot by accident as production.
-
-### Do not build — looks busy, costs GSoC trust
-
-- Magic UI / Aceternity / 3D / custom cursor / glass hero
-- RAG “this sentence is from Wikipedia page N” (the txt diagram)
-- Live Ollama chat as the homepage
-- Training/RunPod/cost dashboards
-- Invented CLI copy buttons
-- Accounts, wallets, visitor prompt logs
-
-Cinematic kits stay as **140–220ms opacity/transform** on copy, expand,
-and pipeline stagger. That is the synthesis of the reference txt.
-
----
-
-## 9. Tests (brief §11)
-
-Run offline. No Wikipedia dump, no GPU.
-
-| File | Asserts |
-| --- | --- |
-| `validate.test.ts` | bad schema → error; empty checks → not success |
-| `status.test.ts` | alias map; `"ok"` is not PASS |
-| `releases.test.ts` | not-released hides download href |
-| `adapter.test.ts` | mock identity mismatch rejected; disabled throws |
-| `navigation.test.ts` | Playwright/Vitest browser: overview → evidence → unreleased releases |
-
-CI later: `frontend.yml` only, on `frontend/**`. Do not edit signing
-or training workflows.
-
----
-
-## 10. Git rules for this repo
-
-- Branch from **current `main`**: `frontend/evidence-docket`
-- Touch **only** `frontend/`
-- Never commit `keys/`, `.venv`, `tmp_smoke`, `src/` LSTM hacks, `goal_state.json`
-- Do not force-push shared branches
-- Draft PR as soon as `npm run dev` shows overview + claim strip
-- Small commits, one concern each (scaffold / types / pages / tests)
-
-Handoff in the PR body: screenshots mobile+desktop, fixture vs public,
-missing parent, unreleased releases, exact maintainer inputs still needed.
diff --git a/frontend/DESIGN.md b/frontend/DESIGN.md
deleted file mode 100644
index e1f609a3..00000000
--- a/frontend/DESIGN.md
+++ /dev/null
@@ -1,406 +0,0 @@
-# OpenVerifiableLLM public site — UI plan
-
-For: ryoari / AOSSIE OpenLLM. Owner of `frontend/` only.
-Stack: React, TypeScript, Vite, ordinary CSS. Static. No backend.
-Models: **not released**. Chat is a labelled shell, not a product.
-
-This is the product cut of `FRONTEND_CONTRIBUTOR_BRIEF.md`. The brief is the
-list of things we must not lie about. This file is what a visitor actually sees.
-
----
-
-## One-line design read
-
-A public **evidence docket** for a research release that does not exist yet:
-calm, typeset, forensic. Not a chatbot landing, not a SaaS dashboard.
-
-If someone closes the tab they should remember the **claim strip** — a persistent
-sentence of what this page is allowed to say — and a **chain with a missing
-link** when a parent is absent.
-
----
-
-## What the site is for
-
-A visitor should leave able to answer:
-
-1. What is OpenVerifiableLLM trying to prove?
-2. Which public sources and transforms are in play?
-3. What was actually checked, by whom, in what scope?
-4. Where are the original artifacts?
-5. Are the models out? (today: no)
-6. Later: how do I download, verify, and run the *exact* weights?
-
-If the homepage looks like ChatGPT, we failed the assignment and the org.
-
----
-
-## Visual lock
-
-Do not use Inter, purple glow, glass cards, three feature tiles, or a giant
-green “VERIFIED” badge.
-
-| Token | Hex | Use |
-| --- | --- | --- |
-| Paper | `#F3F0E8` | Page ground (warm, not cream-terracotta UI kit) |
-| Ink | `#1B2320` | Body and headings |
-| Mute | `#5E6A64` | Secondary text |
-| Rule | `#D4D0C4` | Hairlines, table rules |
-| Pass | `#2C6A4E` | Scoped pass only — never a page background |
-| Fail | `#9B3B32` | Scoped fail |
-| Hold | `#A67C2D` | NOT_RUN / pending / not released |
-| Link | `#215A8F` | Evidence and external refs |
-
-**Type**
-
-- Display / titles: `Source Serif 4`
-- UI / body: `Source Sans 3`
-- Digests, commands, IDs: `Source Code Pro`
-
-**Signature element (one place, then quiet)**
-
-A **claim strip** under the header on every route:
-
-```
-This page may claim: data reconstruction (project-operated) PASS
-It may not claim: a released model · full training replay · answer accuracy
-Snapshot 2026-09-19 · source commit abc1234
-```
-
-Evidence pages add a **chain rail** on the left of the detail view: parent →
-this object → children. A missing parent is an empty notch with “link absent”,
-not a skipped row.
-
-Motion: 150–200ms opacity/transform on expand and copy. Honor
-`prefers-reduced-motion`. No looped animation.
-
----
-
-## Reference kits (`I am giving you these repositories.txt`)
-
-Those six libraries are **interaction textbooks**, not a look to clone:
-
-Motion Primitives · Magic UI · Aceternity UI · React Bits · Cult UI · Animate UI
-
-The same note also says avoid AI slop, random animation, and generic shadcn.
-Taken literally, “cinematic + 3D + cursor-reactive + glass + futuristic AI
-product” **is** that slop, and it fights ryoari’s brief (restrained research
-site, no decorative dashboards).
-
-We steal craft. We do not steal chrome.
-
-| Steal | Where it lands | Refuse |
-| --- | --- | --- |
-| Staggered reveal of the pipeline steps | Overview rail, 40ms stagger, once | Full-page cinematic intro |
-| Hover/press on copy, filter, row | Digests, table rows, buttons | Custom cursor, magnetic buttons |
-| Height/opacity on expand | Evidence JSON, claim details | Accordion everywhere |
-| Route fade (opacity + 8px) | Between the five pages | Shared-element 3D page flips |
-| Skeleton shimmer *only* on evidence fetch | `/evidence` loading | Spinners on static copy |
-| Spatial hierarchy: one column, deep type | Whole site | Bento grids, glass panels |
-| Animated *state* (PASS appearing, FAIL flash) | Result marks | Looping background shaders |
-| Scroll: pipeline pins then releases | Overview only, optional | Scroll hijack, parallax dust |
-
-Implementation rule: `transform` and `opacity` only, 140–220ms, `ease-out`.
-`prefers-reduced-motion` drops travel. No `transition: all`. No particles.
-Do not add Aceternity/Magic UI as dependencies; reimplement the two or three
-patterns we need in CSS.
-
-**The ASCII diagram in that file is a different product.** It is:
-
-```
-user query → LLM → evidence retrieval + claims extraction
- → verification → provenance graph → verified response
-```
-
-That is a **RAG / claim-checking chatbot**. OpenVerifiableLLM is the opposite
-direction: prove the *model and training*, not each chat sentence against a
-Wikipedia article. The contributor brief forbids RAG and article-attribution.
-Do not build that graph as the homepage. The provenance we show is the
-**training evidence chain** (dump → extract → train → replay → release).
-
-If `/try` ever exists for real, a response may link to *model identity*
-(release root, decoding recipe), never “this token came from page 4412.”
-
----
-
-## Information architecture
-
-Five routes. Nothing else in v1.
-
-| Route | Purpose |
-| --- | --- |
-| `/` | What this is, current honest status, three next actions |
-| `/evidence` | Filterable docket of public checks and artifacts |
-| `/evidence/:id` | One object: digest, parents, children, original link |
-| `/releases` | Base card + chat card, both gated until real metadata |
-| `/verify` | Five verification *profiles*, not a magic button |
-| `/try` | Inference shell. Public: disabled. Mock preview planned for M4 |
-
-Nav: Overview · Evidence · Releases · Verify · Try
-Footer: AOSSIE, GitHub, Hugging Face evidence dataset, snapshot time, “this is
-a viewer, not a verifier.”
-
----
-
-## What must be on each screen
-
-### 1. Overview `/`
-
-**Needed**
-
-- One paragraph in plain language: a small model whose *declared* public
- inputs, transforms, and training steps can be inspected and replayed.
-- Availability line in Hold color: **No model release yet.**
-- Three actions only: Explore evidence · View releases · How verification works
-- Pipeline as a **vertical sequence** (this *is* a sequence, so order is allowed):
- Wikipedia dump → extract/exclude ledger → tokenizer/streams → init →
- Wikipedia training → conversation training → full replay → publication
-- Each step shows one of: PASS / FAIL / NOT_RUN / UNAVAILABLE / UNSUPPORTED
- plus **scope** (fixture | pilot | production) and **who** (publisher report vs
- project-operated vs not done)
-- One box: **Provenance is not truth.** Training on Wikipedia does not make a
- sentence a citation.
-
-**Do not**
-
-- Progress percentage (“G01–G02 = 20% done”)
-- Fake completion date
-- Fake assistant reply in the hero
-- A site-wide green badge
-
-**Craft**
-
-The sequence is the page. No card grid. Type + a left rail of status ticks.
-The claim strip is the hero, not a slogan.
-
-### 2. Evidence explorer `/evidence`
-
-This is the product. Mentors will judge GSoC potential here.
-
-**Needed**
-
-- Table/list as the primary UI (brief requires this; diagram is optional extra)
-- Filters: phase, kind, result, scope (fixture / pilot / production)
-- Columns: title · phase · scope · result · performed by · attested by · when · source
-- Click row → detail
-- Empty, loading, fetch-fail, and “zero checks” states that **cannot** look like success
-
-**Groups** (filters, not eight separate apps)
-
-1. Wikipedia source + acquisition
-2. OASST / conversation source
-3. Extraction + inclusion/exclusion
-4. Tokenizer + streams
-5. Init + frozen config
-6. Training boundaries / checkpoints
-7. Reconstruction + replay reports
-8. Release artifacts + clean download
-
-**Craft**
-
-- Result is a word plus a mark, never color alone
-- Fixture rows have a persistent “FIXTURE” tag so they cannot be skimmed as production
-- Superseded reports stay listed, struck through, with a link to the successor
-- Do not fetch dumps, shards, or checkpoints. Show **size + download link**
-
-### 3. Evidence detail `/evidence/:id`
-
-**Needed**
-
-- Title, kind, scope, result, explanation
-- Full digest with copy (truncate visually, copy full)
-- `performedBy` / `attestedBy` / `locallyRecomputed` as three separate fields
- (`locallyRecomputed` is always false on this static site)
-- Parent IDs and child IDs; missing parent = broken chain, not omitted
-- Link to original (GitHub blob, HF file, commit)
-- Expandable escaped JSON, bounded (e.g. 64 KB). Not a second verifier.
-
-**Craft**
-
-Chain rail is here. If parent is missing, the rail has a gap you can see without
-reading the table. That screenshot is the one that belongs in the PR.
-
-### 4. Releases `/releases`
-
-Two panels, stacked on mobile, side by side on desktop: **Base** and **Chat**.
-
-**Until maintainer metadata exists (today)**
-
-- Availability: **Not released yet**
-- Download and Generate controls present but **disabled**, with a sentence next
- to them: why, and what will unlock them
-- Chat panel states it is a derivative of base, so it cannot pretend to be
- “Wikipedia-only”
-
-**When a real release exists**
-
-- Role, repo, immutable revision, release root
-- Param count, file sizes, loader — only from metadata
-- License/attribution from that release
-- Chat → exact base parent
-- Verified *scope* (which profiles passed), links into evidence
-- Download instructions + limitations
-
-**Do not** invent HF repo names, leaderboard scores, or placeholder `.safetensors`.
-
-### 5. Verify `/verify`
-
-Five profiles as five blocks. Each block:
-
-- Name
-- What a PASS is allowed to mean (one sentence from the brief table)
-- What it does **not** mean
-- Status of *this project* for that profile, from the snapshot
-- Command area: **Instructions pending release** until ryoari gives a tested
- command. No copy button on a guessed CLI.
-
-**Needed warning:** complete replay is not a free browser check. If we ever show
-cost/time, label estimate vs actual bill.
-
-**Craft**
-
-This page teaches claim hygiene. It should feel like a legend for the rest of
-the site, not a marketing FAQ.
-
-### 6. Try `/try`
-
-Public build:
-
-- Model picker visible, both options disabled
-- Composer visible, send disabled
-- Banner: **Generation is unavailable until a verified model release is connected**
-- No silent fallback to GPT / Gemini / “any Ollama model”
-
-Mock preview (planned for milestone 4, not wired up yet; `MockAdapter` is
-only used in tests today):
-
-- Mock adapter only
-- Every response carries a visible, sticky label:
- **Example UI response — not generated by a released OpenVerifiableLLM model**
-- Base = completion box; Chat = role-aware thread
-- States: empty, loading, error, retry, identity mismatch (adapter returns a
- different release than selected → reject)
-- Copy response. Receipt download only when a real backend exists
-- No prompt logging, no analytics, no localStorage of visitor chats in v1
-
-This is how we honor “he asked for a chat interface” without shipping a lie.
-
----
-
-## Status vocabulary (render exactly)
-
-Verification result ≠ workflow ≠ browser state.
-
-| Result | Meaning on screen |
-| --- | --- |
-| PASS | This named check passed, inside its reported scope |
-| FAIL | It ran and did not meet requirements |
-| NOT_RUN | Not performed |
-| UNAVAILABLE | Evidence or resources missing |
-| UNSUPPORTED | Cannot be done on this environment/format |
-
-Never map arbitrary strings like `ok` / `complete` / `true` to PASS.
-
-Always show, when a check is displayed:
-
-- profile · artifact · scope · who performed · who attested · locally recomputed?
-
-Independence is not inferred from “two machines” or “a signature.”
-
----
-
-## Data: viewer, not verifier
-
-- Versioned presentation snapshot in `frontend/public/data/`
-- Adapter maps maintainer JSON → view types. Keep source URLs/revisions.
-- Two modes: `fixture` and `public-snapshot`. Failed fetch **must not** fall
- back to a successful mock.
-- `goal_state.json` is navigation for *him*, not a public API. Do not scrape it
- from the client. He supplies an approved snapshot when he has one.
-
-Minimum view types (implement these, not a fantasy backend):
-
-```ts
-type CheckResult = 'PASS' | 'FAIL' | 'NOT_RUN' | 'UNAVAILABLE' | 'UNSUPPORTED';
-type EvidenceScope = 'fixture' | 'pilot' | 'production';
-type ReleaseAvailability = 'not-released' | 'available' | 'withdrawn';
-```
-
-v1 fixtures (enough, not twelve):
-
-1. Public-shaped snapshot: G01/G02-like PASS, models not-released, replay NOT_RUN
-2. Fixture-only success that is visually unskippable as fixture
-3. FAIL hash + missing parent
-4. Superseded FAIL then later PASS
-5. Empty check list (must not look green)
-6. Fetch error / stale snapshot
-
----
-
-## What we will not build (even though we have “freedom”)
-
-These would look busy and would violate the brief or AOSSIE’s less-cloud stance.
-
-- Accounts, wallets, comments, stars
-- Training / RunPod / cost control panels
-- Browser-side full replay or auto-download of weights
-- RAG, “this sentence came from article X”
-- Live Ollama against unverified local models as the default path
-- Invented `ovllm` CLI on a copy button
-- Polling dashboards, graphs of GPU utilization
-- A second design system or a UI kit theme
-
-Freedom is in **how clearly the docket reads**, not in extra product surface.
-
----
-
-## Execution bar (this is the GSoC-looking part)
-
-Mentors remember restraint plus finish, not a 20-page mock.
-
-- Keyboard: skip link, focus ring, no trap in dialogs
-- Status not color-only
-- 200% zoom, mobile table that becomes stacked definition lists
-- Dates with timezone; sizes with units
-- Copy digest announces “copied” to the button, not only visually
-- External links marked external; hashes never re-serialized as “the same bytes”
-- `npm run dev|typecheck|test|build` documented and green
-- GitHub Pages base path considered (`basename` / Vite `base`)
-- Screenshots in the PR: desktop + mobile, including **not released** and
- **missing parent**
-
----
-
-## Build order
-
-| Cut | Ship | Why |
-| --- | --- | --- |
-| M1 | Routes, tokens, claim strip, two fixtures, overview + evidence list | Proves we understood the brief |
-| M2 | Detail chain rail, releases gated, verify legend | The memorable UI |
-| M3 | One maintainer-approved public snapshot adapter | Honesty with real links |
-| M4 | `/try` disabled + labelled mock | Honors the Discord ask without a fake model |
-| M5 | a11y pass, tests, static build, README handoff | Mergeable |
-
-Do not wait for Wikipedia training to finish. Do not wait to implement all
-fixtures before opening a draft PR.
-
----
-
-## Questions for ryoari (only these)
-
-1. Confirm v1 is the **evidence site**, with `/try` gated, not a chat app.
-2. When can we have one **approved public snapshot** (commit + files), vs fixtures only?
-3. Hosting: GitHub Pages under AOSSIE, or wait?
-4. Do not send CLI copy until he pastes a command he has actually run.
-
----
-
-## Why this is the right amount of work
-
-The org does not need another Ollama skin. It needs a public page that can sit
-next to a GSoC demo and not overclaim. That is rare, it matches G09 in
-`PROJECT_GOAL.md`, and it is entirely inside `frontend/`.
-
-Done looks like: a stranger opens `/`, reads that models are unreleased, opens
-evidence, copies a digest, follows a broken parent, and understands the
-difference between “we hashed the dump” and “we replayed every step.”
diff --git a/frontend/README.md b/frontend/README.md
index 694562d7..dc3e2760 100644
--- a/frontend/README.md
+++ b/frontend/README.md
@@ -1,49 +1,76 @@
# OpenVerifiableLLM frontend
-Static public record. It does **not** train, sign, or chat.
+A static site for the OpenVerifiableLLM public record: evidence, releases,
+verification profiles and an inference page. It displays a saved JSON snapshot.
+It has no backend and does not train, sign or run a model.
-Node 20.19+ or 22.12+.
+## Requirements
+
+Node 20.19+ or 22.12+ (required by Vite 7) and npm.
+
+## Commands
```sh
cd frontend
-npm install
-npm run dev
-npm run test
+npm ci
+npm run dev # http://localhost:5173
npm run typecheck
-npm run build
-npx playwright install chromium # once
-npm run e2e
+npm test
+npm run build # writes dist/
+npm run preview # serves dist/
```
-Dev: http://localhost:5173 — HashRouter (`/#/evidence`).
+Routing uses `HashRouter` (`/#/evidence`) and `vite.config.ts` sets
+`base: './'`, so the build can be served from any static host or sub-path.
+
+## Data
+
+Snapshots live in `src/data/fixtures/`. The app loads one, runs it through
+`validateSnapshot` in `src/data/validate.ts`, and shows a data error page if it
+is invalid rather than an empty or passing state. Choose a pack with
+`?snapshot=`, for example `/#/?snapshot=missing-parent`:
-## Snapshot packs
+| `?snapshot=` | File | Shows |
+| --- | --- | --- |
+| `public` (default) | `public-snapshot.json` | Draft project status: G01–G02 practice-run checks passed, both models not released, replay not run |
+| `missing-parent` | `missing-parent.json` | An evidence item whose parent is not in the record |
+| `superseded` | `superseded.json` | A failed check kept after a later pass |
+| `empty` | `empty-checks.json` | No checks, shown as a data error |
-The app never scrapes `goal_state.json`. Default load is the public-shaped pack.
+All of these are fixtures (`"mode": "fixture"`) and the site shows a banner
+while one is loaded. The default pack is a draft of the current project status
+and has not been approved by the maintainer. `public/data/` is reserved for the
+approved snapshot, which is the only file that should use
+`"mode": "public-snapshot"`.
-| `?snapshot=` | What it is |
-| --- | --- |
-| *(default)* `public` | Project-shaped fixture (not an approved public snapshot): G01/G02-like PASS, models **not released**, full replay NOT_RUN |
-| `missing-parent` | Test: child names a missing parent |
-| `superseded` | Test: old FAIL stays after a later PASS |
-| `empty` | Test: zero checks — shown as an error |
+## Structure
-Example: `/#/?snapshot=missing-parent`
-Test packs are not linked in the public footer.
+- `src/data/contracts.ts`: snapshot types.
+- `src/data/adapters/snapshotAdapter.ts`: loads the selected fixture.
+- `src/data/validate.ts`: checks the snapshot and fails closed.
+- `src/snapshot.tsx`: provides the validated snapshot to the pages.
+- `src/pages/`: Overview, Evidence, Releases, Verify and Inference.
+- `src/content/copy.ts`: shared wording for pipeline stages and the FAQ.
+- `src/inference/`: `DisabledAdapter` and `MockAdapter`. Neither is used by
+ the app yet; the Inference page is a disabled placeholder and `MockAdapter`
+ is only used in tests.
+- `tests/`: Vitest tests for validation, fixture loading, release gating,
+ stage results and the inference adapters.
-## This UI will not claim
+Everything is in `frontend/`. Nothing here touches the pipeline in `src/`,
+`project/` or signing keys.
-- Models are available
-- G02 reconstruction, full replay, or independent audit passed
-- `locallyRecomputed` is true
-- A copyable CLI
-- Chat answers come from a released OpenVerifiableLLM model
+## Not done yet
-## Maintainer still needs to provide
+- Real project data, once the maintainer approves a public snapshot (M3).
+- An inference preview using `MockAdapter` behind a build flag (M4).
+- Copyable verification commands. The Verify page says instructions are
+ pending until a tested command exists.
+- Browser tests, a frontend CI workflow and deployment (M5).
-- Approved public snapshot + git revision (M3)
-- Tested verify commands
-- GitHub Pages `base` path
-- Inference backend after a real release
+## Needed from the maintainer
-Do not edit `src/` (pipeline), keys, or `project/` from this app.
+- An approved public snapshot and its source revision.
+- A tested verification command for each profile.
+- Release metadata for the base and chat models when they exist.
+- The hosting target and base path.
From 92fdd08c7390d9e5b3a4cc51311fe45373f91b8e Mon Sep 17 00:00:00 2001
From: panshulthinks
Date: Sun, 27 Sep 2026 14:10:04 +0530
Subject: [PATCH 7/7] docs(frontend): describe project status without gate
references
---
frontend/README.md | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/frontend/README.md b/frontend/README.md
index dc3e2760..289d7e98 100644
--- a/frontend/README.md
+++ b/frontend/README.md
@@ -2,7 +2,8 @@
A static site for the OpenVerifiableLLM public record: evidence, releases,
verification profiles and an inference page. It displays a saved JSON snapshot.
-It has no backend and does not train, sign or run a model.
+It has no backend and does not train, sign or run a model. The project is at
+an early stage and no model has been released yet.
## Requirements
@@ -32,7 +33,7 @@ is invalid rather than an empty or passing state. Choose a pack with
| `?snapshot=` | File | Shows |
| --- | --- | --- |
-| `public` (default) | `public-snapshot.json` | Draft project status: G01–G02 practice-run checks passed, both models not released, replay not run |
+| `public` (default) | `public-snapshot.json` | Draft project status: early practice-run checks passed, both models not released, replay not run |
| `missing-parent` | `missing-parent.json` | An evidence item whose parent is not in the record |
| `superseded` | `superseded.json` | A failed check kept after a later pass |
| `empty` | `empty-checks.json` | No checks, shown as a data error |