Project log
Changelog
What shipped on UI Anatomy and when. Subscribe via the RSS feed.
-
Versioning surface parked
The get_changelog MCP tool and the version/deprecation render were removed after 0/41 components used them. Schema fields stay dormant.
The opt-in versioning metadata from 2026-05-01 (ADR-023) shipped end-to-end — schema fields, an MCP tool, and a render surface — but in the month since, no canonical component populated
since,changelog,deprecated, oraxes.variantDeprecations. Carrying an unexercised lifecycle costs every schema and review pass, so the live surface is parked.Removed. The
get_changelogMCP tool (it returnednullfor all 41 components), and the render: the herosincepill, the view-agnostic changelog section, and the per-slot / per-variant / per-property deprecation badges in the anatomy and axes tables.Kept. The schema fields stay (optional, dormant), and ADR-023 plus the Versioning section of the schema docs remain the design record. The tool and render return the day a component lands its first real published change.
-
SEO + AI-discovery hardening
FAQPage JSON-LD, Schema.org Dataset, Organization sameAs, AI-bot allowlists in robots.txt, top-level DefinedTermSet, IndexNow ping at deploy, public /methodology page.
A coordinated push to surface UI Anatomy to both classical search engines and AI answer engines (Perplexity, ChatGPT, Claude, Gemini).
Citation surface. Every component page now emits a
FAQPagegraph alongsideTechArticleJSON-LD, with question/answer pairs derived from the canonicalwhenToUseprose, related-component differentiators, and documented common mistakes. Modal alone exposes ten Q/A pairs that AI extractors can quote verbatim.Dataset positioning. The site root now declares a
Datasetgraph node with threeDataDownloaddistributions — the JSON catalogue at/api/components.json, the full Markdown corpus at/llms-full.txt, and the index at/llms.txt. Authority signals ship viaOrganization.sameAsandPerson.sameAspointing at the public GitHub repo.Crawler differentiation.
robots.txtkeeps the duplicate-suppressingDisallowset for general crawlers but explicitly allows PerplexityBot, GPTBot, OAI-SearchBot, ChatGPT-User, ClaudeBot, Claude-Web, Google-Extended, and CCBot to crawl/api/,/llms.txt,/llms-full.txt, and the per-page Markdown sidecars — exactly the surfaces designed for AI ingestion.Ontology resolution. The
DefinedTermSetis now a top-level graph node withhasDefinedTerm[]listing every canonical component, sorted alphabetically. Component pages reference the set via@idback-link instead of an inline stub, so the canon resolves as a controlled vocabulary rather than 23 disconnected definitions.Active discovery. Deploys now ping IndexNow with the full sitemap URL list, accelerating index updates on Bing, Yandex, Seznam, and Naver.
E-E-A-T surface.
/methodologyis now a public page renderingdocs/methodology.mddirectly — the same canonical prose that contributors read internally. Author and publisher are emitted asAboutPage+TechArticleJSON-LD withsameAslinks. -
Opt-in versioning metadata
Component-level since + changelog. Per-slot, per-property since + deprecated. Sparse axes.variantDeprecations. New MCP get_changelog tool.
The canonical schema gains lightweight versioning metadata so the canon can record additions and retirements honestly instead of through silent edits.
Component-level fields. A component may declare
since: <semver>to record the version it first entered the canon, pluschangelog: Array<{ version, date, summary }>with unique versions. Both fields are optional and additive.Per-slot and per-property deprecation.
anatomySlotSchemaand both arms ofpropertySchemanow acceptsince?: <semver>anddeprecated?: { since, reason, replacement? }. The deprecation object is.strict(); reason is required prose, replacement is optional.Sparse variant deprecations. Variants stay as bare strings (no shape break across the 23-component roster). Deprecation metadata lives in a parallel
axes.variantDeprecations: Array<{ name, since, reason, replacement? }>with a cross-field refine that validates eachnameagainstaxes.variantsand rejects duplicates.Render. Deprecation pills (warm-accent, line-through) appear on
AnatomyTable,FigmaSlotTable,CodeSlotTable, andAxesTable. The component hero shows asincepill next to the title. A new view-agnosticChangelogSectionrenders below the view content when eithersinceorchangelogis set.MCP. New
get_changelogtool returns{ since, changelog } | null. Tool count is now 19. The full ADR is atdocs/adr/023-versioning.md. -
Side-by-side /compare view
Pure-derivational diff between two canonical components. Anatomy slots, variants, properties, states, schema sections, axe rules, vsRelated prose.
Closes the “designer + DS-maintainer” gap from
docs/personas.md: comparing two canonical components used to require flipping between two browser tabs./compare?a=card&b=tilenow renders an eight-section diff: required-slot deltas, variant set-diff, property kind classification, interactive + data state diff, schema-section presence matrix, axe-rule overlap, plus the canon-pinnedvsRelated.differenceprose when either side cross-references the other.The diff itself is pure-derivational — no schema change.
shared/src/compare.tsexportscomputeCompareDiff(a, b) → ComponentDiffas a deterministic function. The page renders an initial pair server-side, then swaps the diff client-side viaURLSearchParams+fetch('/api/components/<id>.json')calls. Anatomy SVGs link out to component pages instead of duplicating the build-time generator.Header gains a third icon (between view-switcher and search) for the new route.
-
MCP get_implementations + list_implementations
Phase-2 library audit data now reachable through the MCP server. Three Modal audits today (Radix React, Headless UI Vue, Angular CDK Dialog).
The MCP server can now answer “how does Radix’ Dialog diverge from canonical Modal?” without falling back to the JSON API.
Two new tools land:
list_implementationsreturns one row per{libraryId, componentId}pair withdivergenceCount+lastReviewed, sorted by library then component.get_implementations(componentId)returns every library audit for that canonical component as fullImplementationrecords —componentId,libraryId,componentName,exampleCode,divergencelist,rationale,lastReviewed. Empty array when no library has audited the component yet.
The implementations bundle ships at build time as
shared/dist/implementations-bundle.json(~150KB across the three current Modal audits). Worker cold-start re-validates it through the same Zod schema the site uses, so the MCP and HTML pages can never disagree on shape.Tool count moves to 17. New
docs/personas.mddocuments the four user personas (designer / developer / DS maintainer / AI agent) and tracks gap-closure across feature work going forward. -
SEO baseline — JSON-LD, canonical links, sitemap freshness
Per-page TechArticle structured data, view-aware titles + descriptions, sitemap lastmod from canonical lastReviewed dates, robots.txt indexing hygiene, trailing-slash convention.
A three-phase baseline so search engines see UI Anatomy as the same content from multiple angles, not three near-duplicate pages per component.
JSON-LD across the site. New
site/src/lib/jsonLd.tsbuildsWebSite + Organization + WebAPI + SoftwareApplicationfor the home graph andTechArticleper component, with view-aware headlines, breadcrumbs, andDefinedTermmainEntity.Base.astroemits<link rel="canonical">,og:site_name,og:locale,og:url,article:modified_time, and the JSON-LD script.Sitemap freshness. Sitemap entries now carry per-component
lastmodfrom canonicallastReviewed, not the build clock. Different components can show different freshness signals.Robots hygiene.
/api/,/og/,/pagefind/,/llms.txt,/llms-full.txt, and*.md$are disallowed from general indexing — they’re duplicates of the HTML content from a search-engine perspective and would dilute ranking signal. AI agents reach them anyway viaContent-Signal: ai-input=yes.Trailing-slash discipline. Switched Workers Static Assets to
drop-trailing-slashso/components/modalserves directly with 200 instead of redirecting through/components/modal/. Canonical link form, sitemap, and JSON-LD all agree on the no-slash form. -
MCP validate_implementation tool
Heuristic structural conformance check for AI-generated UI code against canonical anatomy / axes / events. Framework-aware event detection.
Closes the developer + AI-agent gap from
docs/personas.md: agents now have a way to self-verify generated UI code against the canon without standing up a full test runner.validate_implementation({ componentId, code, framework })does framework-aware substring detection —on<PascalCase>for React,@event/v-on:/emit('event')for Vue,(event)for Angular, bare names for web components — and reports which canonical required slots, variants, properties, and events appear in the supplied code (and which are missing).This is not a substitute for behavioural assertions. The tool ships with an explicit caveat in its schema: substring search produces false negatives on aliased or minified identifiers. Pair with the per-component a11y-fixture endpoint and a real Playwright + axe-core run.
Tool count moves to 18. New backing utility at
shared/src/validate.tsexported via@uianatomy/shared/validate. -
Cloudflare Workers Static Assets migration
Pages auto-deploy started failing on a converging Pages-config surface. Direct migration to Workers Static Assets via wrangler.jsonc + a single Worker handler.
Cloudflare Pages began rejecting our auto-deploys with a wrangler-suggestion-template error after recent feature commits. The platform is converging Pages onto Workers Static Assets; rather than chase a moving Pages-config surface, we moved directly to the destination.
wrangler.jsoncat the repo root now declares the project (name,compatibility_date,compatibility_flags: ["nodejs_compat"],assets: { directory: ./site/dist, binding: ASSETS, run_worker_first: true, html_handling: drop-trailing-slash, not_found_handling: 404-page }).worker/index.tsconsolidates the previous twofunctions/files into a single dispatch handler —/mcp→ MCP Streamable HTTP transport; otherwise checkAccept: text/markdownand serve the.mdsidecar withx-markdown-tokens+Vary: Accept; otherwise pass through toenv.ASSETS.fetch(request).run_worker_first: trueis required so the Worker can inspect theAcceptheader on requests for matched assets — without it, content negotiation can’t see HTML page requests.Wrangler bumped 3.114 → 4.87. Custom 404 page renders for unmatched URLs with a Levenshtein-distance “did you mean?” suggestion when the path looks like a typoed component slug.