# Fresh — AG-UI × A2UI Native Redesign

**Status:** Phase 0 shipped on `claude/ag-ui-a2ui-redesign-e6i2y5` · Phases 1–4 planned
**Scope:** this repo (`fresh`, static frontend) + the backend service repo (`fresh-backend`, or a new `fresh-mcp`)
**Owners:** Dr. James Sloengaard (product) · engineering via PRs to `main`

---

## 1. Product thesis

Four sentences that rank everything below:

1. **The first-class Fresh experience is the MCP app** — patients meet the practice inside the chat apps they already use (Claude, ChatGPT, and other MCP hosts): ask questions, browse services, read knowledge-base guidance, and request appointments without ever opening a browser.
2. **The website is important but secondary** — it stays, it stays good, and it doubles as the content CDN and the canonical home of every capability; but it is one surface among several, not the product.
3. **Everything we know is readable by AI** — every page has a dedicated AI form (markdown twins, `llms.txt`, canonical-markdown knowledge base), because Dr. Sloengaard's mission is dental knowledge accessible to everyone, and "everyone" now includes everyone's assistant.
4. **Our data is infrastructure for the field** — publicly available dental datasets, curated and hosted as an open API and wrapped in MCP servers, so partners and colleagues (and their agents) skip the fetch-clean-rebuild hassle entirely.

The knowledge base is the content heart of the site: a large, growing library of plain-language dental education that feeds all three surfaces, every article traceable to a named authority. The program — taxonomy, sourcing rules, the full backlog and the production waves — is **`docs/KNOWLEDGE-BASE.md`**. The Open Data API is its structured sibling: nine datasets and ~12,000 records, including real federal data ingested from its publishers (ICD-10-CM dental codes from CMS, CDC/NHANES oral health surveillance, USCS oral cancer rates, CDC water fluoridation) and the KB's own citation layer (`kb-sources`, `kb-articles`). `tools/ingest.js` fetches each source from a commit-pinned URL and verifies its checksum; see `docs/KNOWLEDGE-BASE.md` §8.

## 2. Where we are (post-ralph baseline)

The ralph loop (F0–F8, see `ralph/PLAN.md` + `AI_FIRST.md`) rebuilt the site AI-first:
**bones** (`data/bones.js`, content + capabilities) → **outlet** (`js/outlet.js`, the plug socket) → **plug** (heuristic or LLM brain) → **morphic components** (`js/morphic.js`) — with protocol adapters around it.

What's genuinely standard today vs. bespoke-but-flavored:

| Layer | Today | Verdict |
|---|---|---|
| WebMCP (`js/webmcp-adapter.js`) | real `navigator.modelContext` tools, republished per composition | **native already** — keep |
| AG-UI (`js/agui-bridge.js`) | outbound-only event *view* (snapshot/delta/tool-call/interrupt); no run loop, no agent on the other end | **bridge, not client** — Phase 2 |
| Generative UI trees (`js/portable-tree.js`) | `fresh.ui/v1`, "A2UI/OpenUI-flavored" own schema | **dialect, not the protocol** — Phase 3 |
| MCP app (`mcp-app/`) | sandboxed-iframe UI resource + JSON-RPC contract + manifest sketch; **no live server** | **artifact without a server** — Phase 1 |
| Chatbot | removed — the widget and its floating chrome are gone; an emergency action dials the practice | **retired**; Phase 2's AG-UI client is the replacement conversational channel |
| LLM plug (`js/plug-llm.js`) | bespoke `{manifest, signals} → composition` endpoint (never deployed) | **superseded** by the AG-UI channel in Phase 2 |
| Discoverability | `llms.txt`, markdown twins, canonical-markdown KB, JSON-LD, `ai.html`/`ai.md`, robots.txt | **shipped in Phase 0** |

Phase 0 also fixed two regressions found while auditing: Dr. Sloengaard's photo (dropped when the about page went morphic — bones items now carry `image`/`imageAlt`) and two `.json` artifacts (`mcp-app/manifest.json`, `examples/portable-tree.sample.json`) that the blanket `*.json` gitignore had silently kept out of every commit despite `ralph/PROGRESS.md` recording them as shipped.

**What "native" means here:** today we *translate our shapes into protocol-shaped views*. The redesign inverts that: the real protocols become the interfaces — an AG-UI run loop with an actual agent behind it, actual Google A2UI payloads as the UI language, an actual MCP server hosts can install — and our internals adapt to them, not the reverse.

## 3. Target architecture

One capability layer, many protocol faces; one generative-UI language, many renderers.

```
 SURFACES                        PROTOCOL FACES              FRESH AGENT SERVICE (Cloud Run)
 ─────────                       ──────────────              ───────────────────────────────
 Claude / ChatGPT /              MCP (Streamable HTTP)  ┐
 other MCP hosts        ───────  tools + resources +    │
 ("the first-class app")         MCP Apps ui:// views   │    ┌─ capability layer (one set)
                                                        ├──► │  describe_site · set_intent
 site chat widget       ───────  AG-UI (POST → SSE      │    │  search_knowledge · read_article
 + morphic pages                 event stream)          │    │  get_practice_info · list_services
                                                        ┘    │  request_appointment · escalate_emergency
 visitor's own browser  ───────  WebMCP (in page,            │
 agent                           navigator.modelContext) ──► │  (same fresh_* names in every face)
                                                             │
 partners & colleagues  ───────  Open Data API (/api/,       │  + dataset tools
 (and their agents)              static JSON) + local        │  list_datasets · get_dataset
                                 stdio MCP wrapper (mcp/) ──►│  search_records · lookup_tooth
                                                             │  query_sql (in-memory SQLite)
                                                             │
 crawlers & assistants  ───────  llms.txt · *.md twins       ├─ agent loop (Claude, server-side)
 reading, not driving            · kb/*.md · JSON-LD         ├─ A2UI composer (validated trees)
                                                             └─ content sync ◄── the static site
                                                                (bones.js · data/kb.js · kb/*.md
                                                                 · api/*.json — this repo stays the
                                                                 source of truth and the CDN)
 UI LANGUAGE: A2UI component trees, one composer → three renderers:
   site widget (generative UI in chat) · morphic pages (compositions) · MCP Apps iframe
```

Division of labor between repos:

| Concern | Lives in |
|---|---|
| Content (bones, KB markdown, catalogs), generators, all four surfaces' client code, LLM pages | **`fresh`** (this repo — static, vanilla, no build step) |
| MCP server, AG-UI endpoint, agent loop, A2UI composition/validation, appointment forwarding, rate limiting | **`fresh-backend`** (or a new `fresh-mcp`) on Cloud Run |

The service never duplicates content: it syncs `data/bones.js`, `data/kb.js`, and `kb/*.md` from the deployed site (fetch at deploy, cache with ETag revalidation). Editing an article here updates every surface.

## 4. Protocol adoption

### 4.1 AG-UI — from bridge to client (Phase 2)

Target: the site's chat becomes a real AG-UI client run loop against the agent service.

- **Transport:** `POST /agui/run` with the run input (thread id, run id, message history, frontend tool definitions, page state from `FreshAGUI.getState()`), answered by an SSE stream of typed events. Vanilla ES6 client (`js/agui-client.js`, EventSource/fetch-stream — no npm dependency), consuming the event subset we use: run lifecycle, streaming text messages, tool calls, state snapshot/delta, custom.
- **Frontend tools the agent can call** (executed in-page, results returned into the run):
  `set_composition` (apply an A2UI/portable tree or emphasis order to `<morphic-root>`), `open_article` (navigate/scroll to a KB article), `prefill_booking` (populate the `submit-appointment` form), `escalate_emergency` (existing human-in-the-loop path).
- **State sync:** the existing `agui-bridge` normalized state (`{hero, focus, nodes}`) is sent with each run and updated via `STATE_DELTA` — the bridge's shapes were designed for this and carry over unchanged.
- **What it replaces:** the removed chat widget's bespoke POST (Dialogflow retired) and `plug-llm.js`'s bespoke composition endpoint — the agent now recomposes the page by *calling the `set_composition` frontend tool* instead of returning a proprietary JSON shape. `config.js` gains `AGUI_ENDPOINT`; `LLM_ENDPOINT` is deprecated with it, and `API_ENDPOINT` was removed with the widget.
- **Degrade path (non-negotiable):** with no endpoint configured, the heuristic plug and a static "call us" chat panel keep working exactly as today, including over `file://`.
- **Version discipline:** `ralph/PROTOCOLS.md` records AG-UI v3.0 (Apr 2026); re-verify the current event names against the spec at implementation time and pin the version in the client header + server handshake.

### 4.2 A2UI — from `fresh.ui/v1` to the real tree (Phase 3)

Target: Google A2UI becomes the *only* generative-UI payload; `fresh.ui/v1` becomes a deprecated alias.

- **Adapter:** `js/a2ui-adapter.js` accepts A2UI server→client messages (begin-rendering / surface & data-model updates on a pinned spec version), maintains the surface's component tree + data model, and drives the same targets `portable-tree.js` drives today. `FreshPortableTree.applyTree()` remains one release as a shim that up-converts `fresh.ui/v1`.
- **Catalog mapping:** support a declared subset of the standard A2UI catalog — Card, Column, Row, Text, Image, Button, TextField, DateTimeInput, MultipleChoice, CheckBox, Divider, List, Modal — mapped onto the site design system (morphic sections, `.kb-card`, form controls already generated from `submit-appointment.requires`). Unknown components are skipped with a debug log (the tolerant-walk behavior we already ship).
- **User events:** component actions map to outlet capabilities (`data-action` → dispatch) and to A2UI user-action messages back to the agent — both, so the same tree works with or without a live agent connection.
- **One composer, three renderers:** the service composes/validates trees once; they render in the site widget, as page compositions, and inside the MCP Apps iframe (`fresh-section.html` gains an A2UI mode alongside its bones mode). This is the payoff: booking cards and article cards are authored once and appear identical in ChatGPT, Claude, and on-site.
- **Safety:** trees are untrusted input everywhere — schema-validated server-side against the pinned catalog, escape-interpolated client-side (the `esc()` discipline is already in place).
- **Version discipline:** A2UI is v0.x and moving; pin an exact spec version, vendor its JSON schema into the service, and re-verify message/component names at implementation time. The adapter is the only file allowed to know A2UI wire shapes.

### 4.3 The MCP app — the first-class surface (Phase 1)

Target: `fresh-dental` installable in MCP hosts; a patient can go from "my tooth hurts" to a booked-request without leaving chat.

- **Server:** Streamable HTTP MCP server (official SDK, Node 20) on Cloud Run. Read tools are unauthenticated; session state (e.g. `set_intent`) is per-MCP-session; stateless-friendly.
- **Tools** (contract already sketched in `mcp-app/manifest.json`; names match the WebMCP adapter so every face speaks the same vocabulary):
  - `describe_site` — manifest from bones.
  - `set_intent {topic}` — colors subsequent responses and UI.
  - `search_knowledge {query}` — catalog search over `data/kb.js` (title/summary/topics), returns slugs + summaries.
  - `read_article {slug}` — the canonical `kb/<slug>.md`, verbatim.
  - `get_practice_info` / `list_services` — bones-derived.
  - `request_appointment {…}` — validates against `submit-appointment.requires`, forwards to practice ops (email/webhook), returns a request id. **Request-only semantics in the tool description and the response text: a human confirms every appointment.**
  - `escalate_emergency` — returns the phone number, first-hour guidance (from the emergencies article), and flags the session.
- **Resources:** each KB article as a `text/markdown` resource; `llms.txt` mirrored.
- **MCP Apps UI:** evolve `mcp-app/fresh-section.html` into a small template set — booking card (form generated from `requires`), article card, services browser — declared as `ui://fresh/*` resources, linked from tools via `_meta`, speaking the existing JSON-RPC contract (`ui/ready`, `ui/render`, `tools/call`). Images resolve via absolute site URLs (the server absolutizes bones image paths — the client-side check shipped in Phase 0). Hosts without Apps support get useful plain-text results from every tool: **UI is enhancement, never a requirement.**
- **Distribution:** publish to the MCP registry; document the endpoint on `ai.html`/`ai.md` + `llms.txt` (both already carry the placeholder); provide an `.mcpb` bundle for desktop installs; list as a connector for org users.
- **Ops & safety:** per-IP and per-session rate limits; no PHI persisted beyond the appointment-request forward (define retention with practice ops); log redaction for form fields; every health-adjacent answer carries the education-not-diagnosis framing and emergency routing (ground rules in `ai.md` are the spec).

### 4.4 WebMCP — already native (maintain)

`navigator.modelContext` tools stay as-is; the only Phase 1/2 change is keeping tool names and descriptions byte-identical with the MCP server's, so an agent that learns Fresh in chat can drive the site with the same vocabulary (and vice versa).

## 5. Content platform

- **Markdown is the canonical content format.** KB articles are `.md` files rendered client-side by our ~120-line escape-first renderer; page twins are *generated* from bones (`tools/gen-llms.js`), article shells are *generated* from the catalog (`tools/gen-kb.js`) — chrome is authored once, in the template. `npm run gen` refreshes everything; generated files are committed (a static host serves them as-is).
- **On MDX:** deliberately not adopted. MDX requires a build/bundle step, which this repo's no-build constraint forbids without an explicit owner decision — and its actual job here (interactive components inside content) is covered by A2UI trees composed at runtime and by the morphic layer. If richer authored interactivity is ever wanted, the gate is adopting a static builder (Astro or Eleventy) in Phase 4's "open options," migrating twins + shells into templates at the same time. Until then: plain markdown, zero tooling, perfectly agent-readable.
- **Editorial pipeline:** add an article = write `kb/<slug>.md` + one catalog entry + `npm run gen`. Every article carries the review byline, the education-not-diagnosis footer, and internal links into booking. `updated` stamps live in the catalog; Phase 4 adds a quarterly review cadence over them.

### The Open Data platform

- **Static-first API.** Curated public dental datasets live at `/api/` as versioned JSON with metadata envelopes (fields, source, license, caveats) — generated by `tools/gen-datasets.js`, committed, CORS-open (`_headers`), GET-only, cacheable by any CDN. Shipping datasets as files keeps the zero-infrastructure deploy and makes every revision reviewable in git.
- **Provenance rules (non-negotiable).** Reference datasets (tooth notation, eruption chronology, ICD-10 categories) are stable standard material generated deterministically and identity-tested. Statistical datasets carry a per-record source, URL, and period, and a `precision: approximate` flag — rounded headline figures only, never presented as fresh measurements. All edits go through the generator; hand-edited JSON is forbidden.
- **MCP wrappers, two tiers.** Today: `mcp/fresh-datasets.js`, a zero-dependency stdio server (one file, plain Node) partners run locally — `list_datasets` / `get_dataset` / `search_records` / `lookup_tooth`, plus `query_sql`: read-only SQL (joins, aggregates) over all datasets loaded into an **in-memory SQLite** database via Node's *built-in* `node:sqlite` (22.5+; feature-detected, `PRAGMA query_only`-enforced, still zero npm dependencies). Datasets doubled as resources; protocol-tested in fs and HTTP modes. Phase 1: the hosted practice MCP server exposes the **same dataset tool contract** remotely (Streamable HTTP) so partners can just add a URL; the local file stays as the zero-infrastructure path and the contract's reference implementation.
- **Growth path.** This build environment is network-restricted, so nothing is fetched live; verified extracts from WHO GHO and CDC surveillance APIs (and per-state fluoridation data) land via the backend pipeline in Phase 1+, each as a new generator-managed dataset with retrieval timestamps. Partner requests for new datasets funnel through the contact form and the catalog's issue trail.

## 6. Phases

### Phase 0 — Foundations (this branch — ✅ shipped)

- ✅ Knowledge base: 26 articles across 13 categories; catalog (`data/kb.js`); source registry (`data/sources.js`, 116 verified sources); every article cited and validated by `npm run check` (`tools/check-kb.js`); index with search; generated shells; safe markdown renderer; intent-aware home teaser; Article JSON-LD; emergency escalation on KB pages. The full 426-article program is `docs/KNOWLEDGE-BASE.md`.
- ✅ LLM pages: markdown twins for all pages (generated), KB-aware `llms.txt` with absolute URLs, `ai.html`/`ai.md` agent guide, `robots.txt`, `link rel=alternate` twins on every page.
- ✅ Regressions: Dr. Sloengaard's photo restored (bones `image` support through the morphic renderer and MCP app renderer); gitignored `.json` artifacts restored with explicit exceptions.
- ✅ Plumbing for what's next: capability layer vocabulary extended (`read-kb`, `search_knowledge`/`read_article` in the manifest sketch); root-aware navigation so capabilities work from subdirectory pages.
- ✅ Open Data API v1: four datasets, 101 records (`tooth-notation` with cross-notation identity tests, `tooth-eruption`, `icd10-oral-categories`, `oral-health-indicators`), CORS headers, llms.txt integration — documented on the single agent page (`ai.html`/`ai.md`), which renders the catalog live.
- ✅ Local MCP wrapper for partners: `mcp/fresh-datasets.js` (zero-dependency stdio; in-memory SQLite `query_sql` layer; 40/40 protocol checks in fs + HTTP modes).

### Phase 1 — Ship the MCP app (backend repo; ~the next milestone)

Deliverables: server + tools + resources + Apps UI + registry listing as specced in §4.3; the **dataset tool contract from `mcp/fresh-datasets.js` exposed remotely** (same names, same shapes) plus the dataset fetch pipeline for verified WHO/CDC extracts; content-sync job; ops runbook.
Acceptance: full tool suite green in MCP Inspector; appointment request round-trips to the ops inbox with a request id; article search/read serve catalog + markdown byte-identical to the site; `lookup_tooth` et al. return byte-identical results from the hosted endpoint and the local wrapper; booking + article cards render in at least one Apps-supporting host and degrade to text elsewhere; endpoints published on `ai.md`, `ai.html`, `llms.txt`.

### Phase 2 — AG-UI native channel (both repos)

Deliverables: `/agui/run` SSE endpoint wrapping the same capability layer in an agent loop (server); `js/agui-client.js`, chat surface rebuilt on it, frontend tools, `AGUI_ENDPOINT` config, Dialogflow retirement plan (frontend).
Acceptance: streaming chat end-to-end on the site; agent recomposes the page via `set_composition` (observable as STATE_DELTA in `FreshAGUI`); emergency interrupt reaches a human path; with the endpoint unset, today's behavior is byte-for-byte preserved.

### Phase 3 — A2UI native rendering (both repos)

Deliverables: `js/a2ui-adapter.js` + renderer catalog mapping; server-side composer + schema validation; `fresh-section.html` A2UI mode; committed `examples/a2ui.*.sample.json`; `fresh.ui/v1` shim + deprecation note.
Acceptance: the booking flow, driven by an agent-emitted A2UI tree, completes on the site *and* in the MCP host harness from the same payload; Node round-trip tests over the samples; unknown-component tolerance verified.

### Phase 4 — Hardening, measurement, content ops

Agent-answer evals against KB ground truth (accuracy, no-diagnosis framing, emergency routing); analytics on tool calls, booking funnels per surface, and KB search misses (feeding the editorial backlog); a11y pass (reduced-motion morphs, drop `maximum-scale=1` from viewports, chat focus management); `sitemap.xml` + `llms-full.txt` generation; KB review cadence; deprecations (remove `plug-llm.js`, `fresh.ui/v1` shim, Dialogflow). Open options, explicitly deferred: static builder adoption (MDX gate), NLWeb-style `/ask` endpoint, additional languages for the KB.

## 7. Risks & mitigations

| Risk | Mitigation |
|---|---|
| A2UI is v0.x; shapes may shift | Pin exact version; vendor the schema; all wire knowledge isolated in one adapter file; tolerant-walk rendering |
| AG-UI version drift vs. `PROTOCOLS.md` claims | Re-verify event names at implementation; pin in handshake; client consumes a narrow event subset |
| MCP Apps host support varies | Every tool returns useful text without UI; UI is progressive enhancement (already the artifact's design) |
| Booking abuse / spam via open tools | Rate limits, input validation from `requires`, request-only semantics, ops-side confirmation |
| Health-content liability | Education-not-diagnosis footer on every article and tool response; emergency queries route to humans (`escalate_emergency`, `tel:`); human confirms all bookings |
| Dataset figures cited as authoritative | Provenance rules (§5): per-record citations, `approximate` flags, rounded values only; reference data identity-tested; generator-only edits; verified live extracts replace compiled figures as the pipeline lands |
| Chrome duplication across generated shells | Chrome lives once, in the `gen-kb.js` template; regeneration is idempotent (`npm run gen`) |
| `.gitignore *.json` swallowing artifacts silently | Exceptions required per committed JSON (documented in `.gitignore` itself); lesson recorded here |
| Backend/static drift of content | Service syncs bones/KB from the deployed site with ETag revalidation; never hand-copies |

## 8. Success metrics

- MCP app: installs/active sessions, tool-call success rate, appointment requests originated in chat apps.
- Site: appointment requests via widget vs. form, time-to-first-composition, KB engagement (including the `.md`-vs-`.html` fetch ratio — a direct read on AI readership).
- Quality: agent-answer eval pass rate (accuracy + safety framing), emergency-routing correctness at 100%.
- Content: KB search-miss rate trending down as articles are added.
- Open data: dataset fetches (JSON endpoints), local-wrapper downloads, hosted dataset-tool calls, and partner dataset requests fulfilled.

## 9. Decision log

- **MCP app is the product; the site is a surface + the content CDN.** Ranking set by the practice owner; drives Phase 1 first.
- **One capability layer, many protocol faces.** Same `fresh_*` vocabulary across MCP, WebMCP, and AG-UI frontend tools — an agent's knowledge of Fresh transfers between surfaces.
- **A2UI over growing `fresh.ui/v1`.** We keep our dialect only as a migration shim; betting the UI language on the ecosystem standard is the point of the redesign.
- **KB pages are not morphic.** Long-form reading wants stability; intent-awareness lives in *which* articles get surfaced (teaser, agent picks), not in reshuffling prose. The capability layer stays live on those pages.
- **Markdown, not MDX** (§5) — no build step without an explicit owner decision; A2UI covers interactive content.
- **Generated-and-committed beats build-time** for twins/shells/llms.txt: keeps the no-build deploy (`git push` = deploy) while ending hand-maintained duplication.
- **Static-first data API, zero-dependency local MCP wrapper.** Datasets ship as committed JSON files (reviewable, cacheable, no infra) and the partner wrapper is one plain-Node file with no `npm install` — the hosted MCP endpoint (Phase 1) supersedes it for remote use but never replaces it as the contract's reference implementation. Compiled statistics are always rounded, flagged, and cited; anything presented as a live extract must actually have been fetched. SQL querying rides on Node's built-in `node:sqlite` (in-memory, read-only-enforced) — capability without dependency, feature-detected so older runtimes lose only that one tool.
- **One agent-facing page.** Everything an agent, crawler, or partner needs — markdown access, the Open Data API and its MCP wrappers, on-page protocols, ground rules — lives on the single AI & Agents page (`ai.html`, twin `ai.md`), by owner decision. New agent-facing material extends that page rather than spawning new ones; the deep machine layer (`llms.txt`, `api/*.json`, `kb/*.md`) is not "pages" and stays.
- **Photo regression root cause:** the F4 bones migration modeled team members as icon-only grid items; fixed by giving bones items first-class `image`/`imageAlt` and teaching every renderer (morphic + MCP app) to prefer it. Content migrations must inventory *all* attributes of what they replace.
- **Gitignore lesson:** a blanket `*.json` credentials guard silently ate two shipped artifacts; every committed JSON now needs an explicit exception, and the guard comments say so.
