You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Research: motion-prompt/lucian-level motion, Grand-V in the browser, and whether @codeatlasdev/ui should be WebAssembly
#193
Questions: (1) what makes the motion-prompt and Lucian UX/UI Prompt packs produce faithful motion when an agent ports them; (2) how "GTA V in the browser" (Grand-V / playgta5.com) actually runs in Chromium; (3) should @codeatlasdev/ui be rebuilt 100% in WebAssembly; (4) what roadmap gets us to that motion level with less friction and better performance.
(1) Both packs ship a numbered gallery of working originals plus a spec written from the code's literal numbers, a prompt that forbids re-guessing the design, a license line that must survive the port, and a mandatory report. The magic is the data contract, not the animations.
(2) A source-level Emscripten build of the RAGE engine: wasm64 + shared memory + ~80–116 pthread workers, a D3D11→WebGPU command ring, HTTP Range + OPFS streaming, COOP/COEP mandatory. It is a porting story, not a UI story.
(3) Hypothesis tested and rejected for the component core: wasm DOM frameworks measure 1.24–1.48× vanilla vs Svelte 5 at 1.12×, ship 5–25× more bytes, cannot touch the DOM, and canvas renderers have no a11y tree on the web.
Decision: the ceiling for motion quality is which properties run on the compositor thread and authoring friction; wasm changes neither. We build a CSS/WAAPI motion runtime, a numbered ca-prompt demo catalog, and opt-in GPU/wasm islands (@codeatlasdev/ui/fx) admitted only through an explicit ADR.
Thesis:@codeatlasdev/ui is a brush for AI and humans: everything an app needs is a numbered, reachable reference (demo, prompt, API, token), with zero friction.
Tags: [DERIVED] = computed from raw data; [INFERENCE] = not directly observed. Every number below comes from a cited source or from a file in this repo.
1. What the prompt packs actually are
1.1 The shared pattern
Both packs are the same pattern, invented by Lucian (Oct 2026) and explicitly credited by motion-prompt in its THIRD-PARTY-NOTICES.md:
a numbered gallery of live, dependency-free elements;
a static api/v1/ of plain text files (catalog JSON/TSV, one e/NNN.txt per element);
a zero-dependency Node CLI inside an Agent Skill (search / list / get / kit|tokens / info, plus pick in motion-prompt);
a Claude Code plugin manifest (.claude-plugin/{plugin.json, marketplace.json}).
The agent fetches only the picked numbers and ports them while keeping every motion value.
1.2 Counts and element model
motion-prompt (gallery, FORMAT.md): 5 packs × 24 = 120 elements, global 3-digit ids by pack order (motion 001–024, camera 025–048, ui 049–072, lp 073–096, photo 097–120). Each element is a Canvas 2D pure function of time: cells[i](k, w, h, u) with k a cleared context, u seconds in [0, cycle). No Date, no Math.random at draw time, no state between calls, seamless loop. Rule: "numbers are never renumbered and published names are not renamed." Its meta.json carries a spec field of 3–6 lines: "Take every number from the code — never invent. This is the product."
Lucian UX/UI Prompt (gallery, intro + full prompt): 400 elements (index.jsoncount: 400, 27 categories): 001–100 "parts that make a screen", 101–200 "tools that stand on their own", 201–400 website sections and animations. Each source file is a scoped <style> + <div class="ux uNNN"> + <script> with @meta, @demo (scripted autoplay steps) and @en (string map) comments; the build rejects unscoped selectors or colors outside the 27 --ux-* tokens. Motion values are literal CSS, e.g. transition: transform .5s var(--ux-spring) with --ux-spring: cubic-bezier(.34,1.45,.64,1).
1.3 The six fidelity rules
Why ports come out faithful:
The deliverable is the working original (code), never prose alone.
A spec written from the code's numbers (durations, easing, px, deg).
The prompt names exactly what may change (technology, copy, colors) and what may not (durations, curves, delays, shapes, path). motion-prompt's prompt ends: "Keep the shapes, timing and easing values of the reference implementation as they are, and change only the text, colours and assets."
The agent may not invent ids or reconstruct an element from its name/summary.
Search is narrow: ≤ 8 ids per get, so context stays small.
The license line survives the port, and the agent ends with a report (#id → where, what changed, demo link).
<!-- Lucian UX/UI Prompt #ID · MIT … --> first line
CLI
search/list/get/kit/prompt/info/pick, ≤8 ids
search/list/get/tokens/info, ≤8 ids
Cache
TTL 6 h, disk
TTL 6 h + ETag, content hash per element
Licensing: both MIT; the one-line attribution comment must be kept at the top of every ported element.
2. How Grand-V runs GTA V in Chromium
2.1 What it is, and what it is not
The thing that ran on 2026-10-06 at playgta5.com is a source-level Emscripten build of the RAGE engine: 41 244 rage:: symbols retained in the wasm name section, game.wasm = 63,201,802 bytes (code 48.2 MB, data 7.8 MB, names 6.8 MB), embedded string Build timestamp is: 09:29:05 Oct 5 2026, Rockstar dev paths such as E:\P1\GTA5\SRC\DEV_NG\RAGE\…\effect_d3d11.cpp. It is not an emulator, not video streaming, not decompilation. The lineage is consistent with the December 2023 source leak plus a new wasm/platform/* port layer.
Caveats, stated plainly:
SebasKitten/Grand-V contains no code (README + logo). Its claim to "build on morluto/rea" is wrong as stated: rea is an MCP reverse-engineering tool, not an engine. [INFERENCE] rea was used to produce the static-analysis reports in the scraped mirror.
The only public artefact is the scraped client snapshot shadany7824/playgta5 (JS glue + two investigation reports). Game data (19.7 GiB) is not in it.
The port's author is anonymous; the "AI-ported" claim is unverified (zero AI-vendor strings in the wasm); the 16 October re-release is unverified.
Signal
Observation
Repo contents
README + logo only; no source, no releases
Authorship
SebasKitten is the scraper/re-host; port author anonymous
Money
SOL + BTC donation addresses
Promise
"major re-release Friday 16 October", no artefacts
Domain
registered 2026-10-03, dead within hours on 10-06
AI claim
tweeted, unverified by static scan
2.2 Architecture
page (homepage.html) ← only owns <canvas>; transferControlToOffscreen()
└─ loader.js Worker ← Emscripten "main thread" (static ctors + main(); may block)
├─ wgpu_worker.js Worker ← owns the WebGPU device; executes a command ring in shared wasm memory
├─ io_worker.js Worker ← HTTP Range fetch → OPFS store → copies into shared heap
├─ N pthread Workers ← 80–116 threads normal, 54 in low-mem (~20 MB each)
└─ AudioWorklet (main ctx) ← reads mixer ring in shared memory
Threads: Emscripten pthreads over SharedArrayBuffer + Atomics.wait/notify. Engine threads block on futex words while GPU/IO workers keep real event loops — a blocking C++ game loop without Asyncify or JSPI.
Rendering: WebGPU objects cannot cross threads, so engine threads only emit commands into a ring (47 opcodes: CREATE_TEXTURE, SET_CBUFFERS, DRAW, DISPATCH, READBACK…). Shaders are pre-translated DXBC→WGSL. BC1–BC7 textures; back buffer capped at 3840×2160.
Streaming:fetch with Range into an append-only OPFS store.bin via createSyncAccessHandle(), read straight into the shared heap. Block size 4 KB (the engine mounts ~1 200 RPF archives and reads only TOCs, median 3 KB). Boot set ≈ 580 MB. Measured throughput vs parallel streams: 6 → 3.4 MB/s, 32 → 3.6, 64 → 7.1, 128 → 14.1 MB/s. An earlier sync-XHR + service-worker design piled up ~2.4 GB of garbage and was replaced.
Audio: engine mixer writes a ring in shared memory; an AudioWorkletNode at 48 kHz reads it after the first user gesture.
Headers:Cross-Origin-Opener-Policy: same-origin, Cross-Origin-Embedder-Policy: require-corp, Cross-Origin-Resource-Policy: same-origin — the page refuses to start without crossOriginIsolated.
2.3 Browser support matrix (2026-10)
Feature
Chrome/Edge
Firefox
Safari
Node
Threads / shared memory / Atomics
74
79
14.1
16.4
memory64
133
134
flag only
24.0
Shared and memory64 (what GTA needs)
133+
134+ [INFERENCE]
✗
—
WebGPU
113 (Win/Mac), 121 Android, 144/147 Linux
141 Win, 145/147 Mac, Linux Nightly
26
—
Wasm GC
119
120
18.2
22.0
JSPI
137
153
27
26.0
JS String Builtins
130
134
26.2
flag
Relaxed SIMD
114
145
flag
21.0
exnref exceptions
137
131
18.4
24.15
Net: Chromium 133+ on Windows/macOS in practice; Safari blocked by memory64, Firefox by WebGPU on Linux/Intel Mac.
~20 MB per Worker; 80–116 workers ≈ 1.6–2.3 GB before heap
No DOM/Web API from wasm
every DOM/WebGPU/fetch/audio call is a JS import (86 imports here)
GC'd JS buffers
workers blocked in Atomics.wait never GC → IO moved to a dedicated worker + OPFS
OPFS sync handles
exclusive per file per tab
Timers
5 µs precision only under cross-origin isolation
Spectre isolation
COOP/COEP mandatory → every cross-origin subresource needs CORP/CORS
Blocking main thread
not allowed; engine must live in a worker
What this teaches us: even this build is not "100% wasm". It works because it minimises boundary crossings (command rings, shared-memory slots) and lives entirely off the DOM. A UI library's hot path is exactly the DOM, which wasm cannot own.
3. "UI 100% in WebAssembly": hypothesis tested
3.1 DOM-driving wasm frameworks vs JS (krausest js-framework-benchmark)
[DERIVED]: raw per-run values from results.ts averaged and normalised to vanillajs-keyed, then the official weights applied. Official numbers (Chrome 154 report, MacBook Pro M4) differ by a few %.
Framework (keyed)
Model
Weighted geomean vs vanilla
Create 1k ms
Select row ms
Create 10k ms
Brotli KB
Mem 1k rows MB
vanillajs
JS
1.00
20.7
2.5
213.6
2.5
1.87
wasm-bindgen (raw)
Rust→wasm, no framework
1.06
22.5
2.6
235
14.5
2.87
Solid
JS, fine-grained
1.08
21.7
3.3
228.9
4.5
2.68
Svelte 5
JS, compiled signals
1.12
22.0
4.9
231.9
9.7
2.88
Leptos
Rust, fine-grained
1.24
25.2
3.1
267.3
48.8
5.45
Sycamore
Rust, fine-grained
1.25
24.9
5.5
263.9
47.2
2.10
Dioxus
Rust, VDOM+signals
1.26
24.1
2.9
257.9
114.9
6.12
Yew
Rust, VDOM
1.48
31.6
4.6
326.4
56.8
5.82
Blazor WASM
C#, .NET in wasm
≈4.6
72.2
83.7
696.6
1377
52.7
First paint in the same data [DERIVED]: Svelte 71 ms, Leptos 240 ms, Dioxus 541 ms. Leptos entered light-maintenance mode in May 2026 (leptos#4707).
Second, independent datapoint.realworld-js-framework-comparison (2★, Playwright, median of 3 runs): select row ms React 250.6 / Vue 29.0 / Svelte 15.6 / Solid 222.9; create 10k rows ms React 828.8 / Vue 270.4 / Svelte 472.1 / Solid 235.3; bundle gzip React 62.4 / Vue 28.1 / Svelte 18.1 / Solid 8.1 KB. No cross-framework LLM-codegen benchmark that includes Svelte was found (DesignBench covers React/Vue/Angular; sveltejs/ai is Svelte-only), so "Svelte is best for agents" is a hypothesis. The lever that is proven is the data contract of §6.
3.2 Why wasm cannot be faster at DOM work
No direct DOM access. Wasm imports/exports numbers and opaque externrefs; every createElement/setAttribute goes through a JS shim. wasm-bindgen's README still lists "no JS shims" as a future goal; ACM Queue's answer to "when will wasm get DOM support?" is "might never" (dl.acm.org/doi/10.1145/3769472).
Strings are the tax. DOM APIs take JS strings; linear memory holds UTF-8 → copy + decode per call. JS String Builtins (finished, spec 3.0; Chrome 130 / Firefox 134 / Safari 26.2) remove the glue for string ops, but mostly help WasmGC languages.
The boundary itself is cheap (raw wasm-bindgen = 1.06×; ~2.5–4.5 ns per call measured by Mozilla). The extra 20–40% in Leptos/Dioxus/Yew is framework bookkeeping, allocation and string traffic.
Canvas UIs lose native forms, IME, text selection, find-in-page, autofill, spellcheck, scroll physics, pinch-zoom, print and SEO, and must reimplement them (worse).
3.4 Glass blur: is a custom GPU pass a win?
Chrome's Skia blur algorithm (downsample until σ ≤ 4, convolve, upsample), reimplemented in WebGPU at 3840×2160 on an M4 Max: radius 5 → 0.94 ms, r = 1000 → 0.42 ms. Naïve separable Gaussian: 297 ms at r = 1000. "Smol Gaussian" (improved Skia): 1.27 → 0.62 ms, i.e. within ±30% of Skia (aras-p, 2026-10-01). No algorithmic win is left.
The real cost is stacking: 8 backdrop-filter layers over 1920×1080 ≈ 200 ms/frame on mid-tier GPUs; keep 2–3.
3.5 Svelte's floor
packages/ui/size-limit.json: "floor: Divider only": 18863 B gzip and "root: Button only": 27530 B gzip. That entire client runtime is smaller than any wasm UI runtime in the table above.
3.6 Answer
Not supported by the evidence for a DOM component library in 2026: a wasm rewrite would be ~10–20% slower at DOM work, 5–25× heavier, lose a11y/forms/SEO parity, and still need JS glue — so not "100% wasm" anyway. A canvas-rendered library can be visually richer and shader-heavy, but it is a different product (app shell, game UI, editor), not a design system with OKLCH tokens, popover, View Transitions and screen-reader semantics.
4. Where wasm and WebGPU do pay off for us
4.1 Decision matrix
Feature
Technology
Expected gain vs today
Bundle cost (gzip)
a) OKLCH palette / contrast
JS (keep)
0 (tens of µs already; build-time)
+0
b) color-mix / gamut per frame
CSScolor-mix(), oklch()
off-thread, 0 JS
+0
c) Text measure
Canvas/DOM
n/a (wasm has no fonts/DOM)
+0
d) Springs ≥ 1 000 elements
JS SoA + batched writes; WebGPU/wasm-SIMD only through an explicit ADR
An SVG filter works on the element's own pixels (SourceGraphic), not on what is behind it. fill="none" → transparent source → feDisplacementMap displaces nothing → feComposite operator="in" keeps nothing. [INFERENCE — verify with VRT] The visible glass is entirely the CSS backdrop-filter + tint + rims, and 4 of 6 optics (depth, curvature, sheen, dispersion) are unused. Fix: backdrop-filter: url(#id) behind @supports (Chromium only), mapping the unused optics to CSS, or delete the overlay.
Shiki loads Oniguruma wasm.packages/ui/src/lib/utils/highlighter.ts:8 calls createHighlighter from "shiki" (default engine = Oniguruma compiled to wasm). Switching to shiki/core + createJavaScriptRegexEngine removes a wasm download; shiki's docs say all built-in languages are supported since 3.9.1. This is a guaranteed byte win, by removing wasm.
DataTable.svelte simple mode sorts with String(...).localeCompare(...) per compare and filters with String(v).toLowerCase().includes(q) per keystroke, with no virtualization. For 100 k rows that is ~1.7 M string allocations per sort. Fix in JS: precomputed sort keys, a reused Intl.Collator, sorting a Uint32Array index, a pre-lowercased search blob per row. No wasm.
4.3 Admission thresholds for wasm (all must hold)
Beats the optimized JS baseline (typed arrays, precomputed keys, no per-call allocation) by ≥ 2× at p95 on the input size the component actually ships.
End-to-end gain ≥ 2 ms per interaction or ≥ 1 ms per frame (below that it is invisible at 60/120 Hz).
Bundle delta ≤ 5 KB gzip on a root import path, otherwise a lazy chunk ≤ 30 KB gzip loaded only after intent.
Cold start (fetch + compile + instantiate) ≤ 50 ms at 4× CPU throttle, with a JS fallback as the shipped default under SSR tests.
No COOP/COEP, no SharedArrayBuffer.
4.4 Toolchain, if something ever passes
Rust + wasm-bindgen --target web (plain ESM + .wasm URL, bundler-agnostic) + wasm-opt -Oz; wasm-pack adds npm packaging noise that svelte-package does not need. Floor ≈ 7–10 KB gzip per Rust module once it allocates. Loading:
import{BROWSER}from"esm-env";exportfunctionloadWasm(){if(!BROWSER)thrownewError("wasm is client-only");return(p??=import("./ca_wasm.js").then(async(m)=>{awaitm.default(newURL("./ca_wasm_bg.wasm",import.meta.url));returnm;}));}
new URL(…, import.meta.url) is the only asset reference every consumer bundler understands; ?init/?url are Vite-only. scripts/size-limit.ts gets a new asset entry kind (gzip of a raw file) under the same ratchet.
5. The motion runtime that actually reaches that level
5.1 What runs on the compositor
Chromium's kCompositableProperties (compositor_animations.cc): backdrop-filter, filter, opacity, rotate, scale, transform, translate, plus background-color and clip-path through native paint worklets.
Design rule: every ca-motion-* utility is expressible with transform/opacity (+ filter, + clip-path for reveals). Layout properties only as CSS transitions on discrete state changes.
5.2 Drivers
Driver
Thread
Per-frame JS
Fits
CSS transition
compositor if compositable
none
hover/press/focus/state — ~90% of component motion
CSS @keyframes
compositor if compositable
none
enter/exit, loops, staggers
@starting-style
compositor
none
enter on mount / display:none → block (Chrome 117, Firefox 129, Safari 17.5)
WAAPI element.animate()
compositor if compositable
none after creation
FLIP, crossfade, Svelte transition:/animate:
Scroll-driven animation-timeline
compositor
none
reveal, parallax, progress
View Transitions (same-doc)
compositor (GPU snapshots)
none
route/tab/theme/list reorder
svelte/motionSpring/Tween
main thread
yes, rAF every frame
gesture-linked values only
GSAP / anime.js
main thread
yes
timelines, SVG morph
motion mini animate
WAAPI → compositor
none after creation
a JS sequencer, if ever needed
5.3 Verified Svelte 5.57.2 facts
transition:/in:/out: compile to WAAPI. node_modules/svelte/src/internal/client/dom/elements/transitions.jsL441–447 pre-samples keyframes once (n = Math.ceil(duration / (1000/60)), 240 ms → 15 samples) and then calls element.animate(keyframes, { duration, fill: 'forwards' }).
L464–473: only a transition that supplies tick runs a rAF loop() on the main thread. css-only transitions have zero per-frame JS. Never pass tick.
animate:flip uses the same WAAPI path with translate/scale only. slide animates height/padding/margin → layout per frame.
drives panel width every frame: JS tick + style + layout of shell and content + paint.
The repo's createPresence (primitives/presence.svelte.ts) already gates unmount on el.getAnimations({ subtree: true }), so it works with CSS and WAAPI alike and collapses instantly when durations are zero.
5.4 Springs as linear() easings
packages/ui/src/lib/styles/tokens.css:77 today: --ca-ease-spring: cubic-bezier(0.175, 0.885, 0.32, 1.275); — an overshoot, not a spring. CSS linear() (Chrome 113, Firefox 112, Safari 17.2) can encode a sampled damped oscillator as linear(0, 0.013, 0.05, …, 1.02, 1.008, 1), generated with Jake Archibald's generator. Zero JS per frame. Fallback order: keep the cubic-bezier first, override inside @supports (animation-timing-function: linear(0, 1)). A linear() spring has a fixed settle time, so duration and easing ship as one token: --ca-spring-snappy: 380ms linear(...). 5 presets ≈ 2–3 kB raw, ~0.8 kB gzip [INFERENCE]; they go in a new motion.css with its own size-limit entry, not in core.css.
Chrome 129 only → keep grid-template-rows: 0fr → 1fr
field-sizing: content
auto-growing TextArea
Chrome 123, Firefox 152, Safari 26.2
@property
animate custom props
Chrome 85, Firefox 128, Safari 16.4
Scroll-driven animation-timeline
reveal, header shrink, progress
Chrome 115, Safari 26, Firefox 159 (BCD)
View Transitions same-doc
route/tab/theme
Chrome 111, Safari 18, Firefox 144
View Transitions cross-doc
MPA
Chrome 126, Safari 18.2, Firefox ✗
Houdini:@property → use (Baseline). Paint API → Chromium-only, decoration-only progressive enhancement. Animation Worklet → dead (origin trial Chrome 71–74 only). Layout Worklet → flag only.
5.6 Libraries
Library
Size
Runs on
motion (mini animate)
2.6–3.2 kB (full animate 18–22 kB)
WAAPI → compositor
GSAP 3.15
27.35 kB gzip core
rAF main thread
anime.js 4.5
40.3 kB gzip full
rAF main thread
Rive @rive-app/canvas
2 057 365 B wasm + 570 043 B JS (uncompressed)
wasm canvas
dotLottie
1 238 072 B wasm + 165 kB JS
wasm canvas (worker build available)
Zero new runtime deps in core. Svelte's own element.animate path + CSS covers the catalog. Rive/Lottie are content formats, never in root entries.
5.7 Reduced motion, encoded by class
WCAG 2.2 SC 2.3.3 (AAA): motion triggered by interaction must be disableable; opacity/color fades are not "motion".
SC 2.2.2 (A): auto-playing motion > 5 s must be pausable — AuthLayout blobs loop at 9 s / 12 s / 15 s and need a pause control or removal under reduced motion.
Today --ca-motion-scale: 1 → 0 zeroes every duration. Proposal: three axes per token — kind (fade / move / morph / loop), distance (xs 4px … xl 64px), duration — plus --ca-motion-distance-scale: 0 under reduced motion, so movement collapses while a 120 ms opacity fade survives. A user-level [data-ca-motion="reduced"|"full"] override on <html>.
tokens.css — --ca-duration-{press,fast,base,slow}, --ca-ease-{standard,decelerate,accelerate,emphasized,spring}, --ca-motion-scale; already richer than Lucian's --ux-ease/--ux-spring
So ~80% of the plumbing exists. What is missing:
Need (from the packs)
Today
Gap
Stable 3-digit numbering, append-only
demos addressed by path
id registry
Per-element summary / spec / tags / shape
title + description in MDX <Demo> props only
sidecar metadata
Static text API
components.json/tokens.json (API-level)
api/v1/ per demo
License line
none
first-line comment in emitted text
Agent tools
component/token/guide lookups
search_demos, get_demo
Reporting rule
none
report line in SKILL.md
Plugin marketplace
none
.claude-plugin/*
Note: our demos import @codeatlasdev/ui by design — the "kit" is the library itself. ca-prompt is a usage catalog, not a copy-paste catalog, and that is the right product for a component library.
6.2 Proposal
Registrysite/src/demos/catalog.ts, checked in, append-only: [{ id: "001", path: "inputs/button/States" }, …]. The build fails if a demo has no id or an id's path disappeared.
Sidecar<Variant>.meta.json: { title, summary, use, spec[], tags[], shape }, with spec written from the demo's literal code values (motion-prompt's rule).
buildDemos() in build-manifest.ts emits dist/demos.json (ships in the tarball) and site/public/api/v1/{catalog.json, en/catalog.tsv, en/e/NNN.txt, en/p/NNN.txt}. Every e/NNN.txt starts with: <!-- @codeatlasdev/ui demo #NNN · MIT License (c) CodeAtlas · <url> -->
followed by a header (Element, What it shows, Spec, Tags, Needs: @codeatlasdev/ui@<ver>, Components, Live demo) and the raw .svelte source.
MCP:search_demos(query, group?) and get_demo(id | "#012" | url), capped at 8 ids per call.
Skill: SKILL.md section "Pick a demo by number" (ids, ?pick=, pasted picks, ≤ 8 get_demo, port rules, report line).
Optionalcodeatlas-ui pick: opens the docs site with a token, listens on 127.0.0.1, prints picks; ?pick=012,045 links on the site. Reference: motion-prompt implements ?pick= and the pick listener in assets/site.js:594-598,698 and motion-prompt.mjs:431-433, documented in its SKILL.md (not FORMAT.md).
Plugin:.claude-plugin/{plugin.json, marketplace.json} at the repo root.
6.3 Prompt template (p/NNN.txt)
Use @codeatlasdev/ui demo #NNN "<title>" (<group>/<component>).
What it shows: <summary>. When to use: <use>.
Spec: - … (lines from meta.spec)
Check first: Svelte 5 + @codeatlasdev/ui installed? theme in src/lib/theme.ts? Kit 2 or 3?
Apply: import only from "@codeatlasdev/ui[/charts|/table|/code]"; keep component names, props and
variants of the reference as they are; no layout CSS, --ca-* tokens only (SKILL.md rules 1–7);
replace sample data/copy with the project's; keep the first comment line.
Report: #NNN → file, what changed, demo link; what you could not check (light/dark, 375px, keyboard, reduced motion).
Reference: <api url>/en/e/NNN.txt Components API: ca-ui-mcp get_component(...)
6.5 Showcase
A /gallery page on the Astro site lists every numbered demo as a live card (lazy, mounted when visible), filterable by group and tag. Each card has a Pick toggle; a sticky bar offers Copy picks (text I picked these N from the @codeatlasdev/ui gallery: #012 …) and Send to AI (navigates to the ?cb= callback when present, motion-prompt's model). Deep links: /gallery#012 scrolls to a card, ?pick=012,045 preselects.
6.4 Effort
Step
Files
Estimate
id registry + check
site/src/demos/catalog.ts, build-manifest.ts
1–2 h
meta sidecars for ~230 demos
*.meta.json
the real work; AI-assisted from code, 1 day review
buildDemos() + api/v1 emit
build-manifest.ts
2–3 h
MCP tools + skill text
ca-ui-mcp.ts, SKILL.md, DEMOS.md
1–2 h
pick flow (optional)
codeatlas-ui.ts, Demo.astro
3–4 h
plugin manifests
.claude-plugin/*
15 min
7. Roadmap
Each phase becomes issues labeled area:motion / area:perf / area:docs / enhancement, one worktree per issue per the AGENTS.md workflow.
flowchart LR
P1[P1 Motion runtime] --> P3[P3 ca-prompt catalog]
P1 --> P6[P6 Native navigation]
P2[P2 Byte wins and fixes]
P3 --> P4[P4 Surface engines]
P5[P5 SEO/PWA by default]
P7[P7 fx islands]
Loading
The former P0 (Measure) was dropped: wasm is already known to be slower for DOM work; no in-house wasm is planned, so no harness.
P1 — Motion runtime (depends on nothing)
motion.css + size-limit entry; --ca-move-*, linear() spring tokens, @property. No component changes.
Docs: motion catalog page (kind / distance / duration and reduced-motion mapping per utility).
Acceptance: zero per-frame JS for UI chrome (no rAF loop outside gesture-driven values); reduced-motion keeps fades and drops movement; size budget for motion.css holds.
Why: this is where motion-prompt/lucian-level quality comes from — compositor-only properties and tokens an agent can copy by number.
P2 — Byte wins and fixes (independent)
shiki → shiki/core + createJavaScriptRegexEngine.
DataTable: sort keys, reused Intl.Collator, index sort, lowercase blob, virtualization.
LiquidGlass: prove the overlay is inert with VRT, then backdrop-filter: url() behind @supports or delete the overlay and the 4 dead optics.
Acceptance: fail-before/pass-after tests per AGENTS.md rule 0; size-limit only goes down.
Why: the only measurable perf/byte wins on the table today, and all of them remove wasm or need none.
P3 — ca-prompt catalog (depends on P1 for the motion demos' spec lines)
Wave 1: append-only id registry + build check; buildDemos() → dist/demos.json + site/public/api/v1; MCP search_demos / get_demo; skill section "Pick a demo by number" + .claude-plugin manifests; /gallery showcase (§6.5).
Wave 2: meta sidecars with spec lines for all 236 demos (AI-assisted, reviewed); codeatlas-ui pick CLI (127.0.0.1 token listener).
Wave 3: P4/P5/P6 below, one issue per bullet.
Acceptance:get_demo("#012") returns the license line, spec and source; the build fails on an unregistered demo.
Why: this is the friction killer — the same data contract that makes the two packs port faithfully.
P4 — Surface engines (glass is one engine, not the identity)
Today:styles/surface.css already paints every surface from --ca-surface-* with material="auto" following html[data-style] (recipes solid/frosted/flat/outlined/neumorphic in materials.css:371-472; materials.css:360 says default = solid, glass opt-in).
Gap: 22 component files still declare backdrop-filter locally.
Work: migrate those 22 to .ca-surface / material="auto"; a lint:tokens rule rejecting backdrop-filter outside styles/surface.css and styles/materials.css; MCP tool list_surfaces generated from the [data-style="…"] selectors in materials.css; per-platform defaultStyle documented for Tauri WebKitGTK [INFERENCE: validate with feature detection].
P5 — SEO/PWA excellent by default
Today:seo/ ships Seo, renderOgImage/ogImageRoute/ogCard, JSON-LD builders, sitemapXml, robotsTxt, llmsTxt, markdownNegotiation, manifest, appleMeta, iconLinks; init wires none of it. No service worker, offline route or install prompt in code (docs only, getting-started/pwa.mdx). The brand manifest (brand.ts:208-214) is display:"browser" with light colors only, while manifest() defaults to standalone.
Work:init scaffolds manifest.webmanifest/+server.ts, robots.txt/+server.ts, sitemap.xml/+server.ts, src/service-worker/index.ts (precache shell + offline route via $app/service-worker) and <Seo> in the root layout; generateBrand emits display:"standalone" with defaultScheme colors and a manifest id; Seo gains a default OG image, twitter:title/description and lang; an InstallPrompt component (Chromium beforeinstallprompt, iOS instructions fallback); Lighthouse CI (SEO/PWA/perf) in validate.
P6 — Native-feeling navigation
Facts: Kit layouts persist like Next ("Layouts do not rerender"); Kit preload data-sveltekit-preload-data hover|tap (template default hover) and preload-code eager|viewport|hover|tap, disabled with "false"; navigating from $app/state; onNavigate + startViewTransition; server-load promise streaming. Kit has no loading file; Svelte's <svelte:boundary>pending snippet fills that role (SSR renders pending). NN/g: nothing under 1 s, skeleton for full-page loads < 10 s, spinner for a single module, percent bar ≥ 10 s.
Work:<LoadingBoundary delay=200 min=500> wrapping <svelte:boundary> with pending and aria-busy; a skeleton prop on data components (SimpleTable, KpiCard, Avatar, TextField, GlassCard) rendering same-size placeholders (CLS 0); <NavigationProgress> reading navigating; a data-pending attachment per link; viewTransition() from P1; AppShell test "zero remounts across 10 navigations"; Skeleton.svelte shimmer moves from background-position (:86-89, repaint per frame) to a translate pseudo-element; docs page "App navigation" with preload defaults (<body data-sveltekit-preload-data="hover">, preload-code="viewport" on nav links).
P7 — @codeatlasdev/ui/fx islands
<canvas> behind an attachment, role="img" with a label or aria-hidden when decorative, paused offscreen and on visibilitychange, WebGPU with WebGL2 fallback.
Candidates: shader hero / LiquidBackdrop over self-rendered content, particle/physics fields, optional Rive wrapper lazy-loading canvas-lite (222 KB brotli).
Acceptance: admitted only through an explicit ADR (no perf harness); never in root entries; no COOP/COEP.
Why: this is where GPU and wasm genuinely win (pixels, particles, shaders), isolated from the a11y-critical DOM core.
8. Open questions for the community
Catalog ids: global append-only (Lucian) vs per-group ranges (motion-prompt)? Decision taken for Wave 1: global append-only, group recorded as a field (renumbering risk zero, groups can move).
Is there real demand for an official Rive wrapper (@codeatlasdev/ui/rive), or is a docs recipe enough?
Should the catalog ship Portuguese summaries/specs via meta.lang, with English as the source language?
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
TL;DR
@codeatlasdev/uibe rebuilt 100% in WebAssembly; (4) what roadmap gets us to that motion level with less friction and better performance.ca-promptdemo catalog, and opt-in GPU/wasm islands (@codeatlasdev/ui/fx) admitted only through an explicit ADR.ca-prompt→ P4 surface engines → P5 SEO/PWA by default → P6 native navigation → P7fxislands.@codeatlasdev/uiis a brush for AI and humans: everything an app needs is a numbered, reachable reference (demo, prompt, API, token), with zero friction.Tags:
[DERIVED]= computed from raw data;[INFERENCE]= not directly observed. Every number below comes from a cited source or from a file in this repo.1. What the prompt packs actually are
1.1 The shared pattern
Both packs are the same pattern, invented by Lucian (Oct 2026) and explicitly credited by motion-prompt in its
THIRD-PARTY-NOTICES.md:api/v1/of plain text files (catalog JSON/TSV, onee/NNN.txtper element);search / list / get / kit|tokens / info, pluspickin motion-prompt);.claude-plugin/{plugin.json, marketplace.json}).The agent fetches only the picked numbers and ports them while keeping every motion value.
1.2 Counts and element model
motion-prompt (gallery, FORMAT.md): 5 packs × 24 = 120 elements, global 3-digit ids by pack order (
motion001–024,camera025–048,ui049–072,lp073–096,photo097–120). Each element is a Canvas 2D pure function of time:cells[i](k, w, h, u)withka cleared context,useconds in[0, cycle). NoDate, noMath.randomat draw time, no state between calls, seamless loop. Rule: "numbers are never renumbered and published names are not renamed." Itsmeta.jsoncarries aspecfield of 3–6 lines: "Take every number from the code — never invent. This is the product."Lucian UX/UI Prompt (gallery, intro + full prompt): 400 elements (
index.jsoncount: 400, 27 categories): 001–100 "parts that make a screen", 101–200 "tools that stand on their own", 201–400 website sections and animations. Each source file is a scoped<style>+<div class="ux uNNN">+<script>with@meta,@demo(scripted autoplay steps) and@en(string map) comments; the build rejects unscoped selectors or colors outside the 27--ux-*tokens. Motion values are literal CSS, e.g.transition: transform .5s var(--ux-spring)with--ux-spring: cubic-bezier(.34,1.45,.64,1).1.3 The six fidelity rules
Why ports come out faithful:
get, so context stays small.#id → where, what changed, demo link).1.4 Side by side
cells[i](k,w,h,u)pure fn of time<style>+<div class="ux uNNN">+<script>packs/<pack>/pack.js+meta.jsonsrc/elements/NNN-slug.htmlwith@meta/@demo/@enspec[]lines written from code numberskit/<pack>.jstokens.csspickCLI + 127.0.0.1 token listener + "Send to AI" /?pick=#NNNto the skillp/<id>.txtper element// Motion Prompt #ID · MIT …first line<!-- Lucian UX/UI Prompt #ID · MIT … -->first lineLicensing: both MIT; the one-line attribution comment must be kept at the top of every ported element.
2. How Grand-V runs GTA V in Chromium
2.1 What it is, and what it is not
The thing that ran on 2026-10-06 at
playgta5.comis a source-level Emscripten build of the RAGE engine: 41 244rage::symbols retained in the wasm name section,game.wasm= 63,201,802 bytes (code 48.2 MB, data 7.8 MB, names 6.8 MB), embedded stringBuild timestamp is: 09:29:05 Oct 5 2026, Rockstar dev paths such asE:\P1\GTA5\SRC\DEV_NG\RAGE\…\effect_d3d11.cpp. It is not an emulator, not video streaming, not decompilation. The lineage is consistent with the December 2023 source leak plus a newwasm/platform/*port layer.Caveats, stated plainly:
SebasKitten/Grand-Vcontains no code (README + logo). Its claim to "build on morluto/rea" is wrong as stated:reais an MCP reverse-engineering tool, not an engine. [INFERENCE] rea was used to produce the static-analysis reports in the scraped mirror.shadany7824/playgta5(JS glue + two investigation reports). Game data (19.7 GiB) is not in it.2.2 Architecture
new WebAssembly.Memory({initial: 3 GiB, maximum: 262144n, shared: true, address: "i64"})→ 3 GiB initial, 16 GiB max (the V8 memory64 cap).SharedArrayBuffer+Atomics.wait/notify. Engine threads block on futex words while GPU/IO workers keep real event loops — a blocking C++ game loop without Asyncify or JSPI.CREATE_TEXTURE,SET_CBUFFERS,DRAW,DISPATCH,READBACK…). Shaders are pre-translated DXBC→WGSL. BC1–BC7 textures; back buffer capped at 3840×2160.fetchwithRangeinto an append-only OPFSstore.binviacreateSyncAccessHandle(), read straight into the shared heap. Block size 4 KB (the engine mounts ~1 200 RPF archives and reads only TOCs, median 3 KB). Boot set ≈ 580 MB. Measured throughput vs parallel streams: 6 → 3.4 MB/s, 32 → 3.6, 64 → 7.1, 128 → 14.1 MB/s. An earlier sync-XHR + service-worker design piled up ~2.4 GB of garbage and was replaced.AudioWorkletNodeat 48 kHz reads it after the first user gesture.Cross-Origin-Opener-Policy: same-origin,Cross-Origin-Embedder-Policy: require-corp,Cross-Origin-Resource-Policy: same-origin— the page refuses to start withoutcrossOriginIsolated.2.3 Browser support matrix (2026-10)
Net: Chromium 133+ on Windows/macOS in practice; Safari blocked by memory64, Firefox by WebGPU on Linux/Intel Mac.
2.4 Hard limits nobody can engineer around
Atomics.waitnever GC → IO moved to a dedicated worker + OPFSWhat this teaches us: even this build is not "100% wasm". It works because it minimises boundary crossings (command rings, shared-memory slots) and lives entirely off the DOM. A UI library's hot path is exactly the DOM, which wasm cannot own.
3. "UI 100% in WebAssembly": hypothesis tested
3.1 DOM-driving wasm frameworks vs JS (krausest js-framework-benchmark)
[DERIVED]: raw per-run values fromresults.tsaveraged and normalised tovanillajs-keyed, then the official weights applied. Official numbers (Chrome 154 report, MacBook Pro M4) differ by a few %.First paint in the same data
[DERIVED]: Svelte 71 ms, Leptos 240 ms, Dioxus 541 ms. Leptos entered light-maintenance mode in May 2026 (leptos#4707).Second, independent datapoint.
realworld-js-framework-comparison(2★, Playwright, median of 3 runs): select row ms React 250.6 / Vue 29.0 / Svelte 15.6 / Solid 222.9; create 10k rows ms React 828.8 / Vue 270.4 / Svelte 472.1 / Solid 235.3; bundle gzip React 62.4 / Vue 28.1 / Svelte 18.1 / Solid 8.1 KB. No cross-framework LLM-codegen benchmark that includes Svelte was found (DesignBench covers React/Vue/Angular;sveltejs/aiis Svelte-only), so "Svelte is best for agents" is a hypothesis. The lever that is proven is the data contract of §6.3.2 Why wasm cannot be faster at DOM work
externrefs; everycreateElement/setAttributegoes through a JS shim. wasm-bindgen's README still lists "no JS shims" as a future goal; ACM Queue's answer to "when will wasm get DOM support?" is "might never" (dl.acm.org/doi/10.1145/3769472).3.3 Canvas / WebGPU-rendered UI engines
--wasm)[INFERENCE]<canvas>Canvas UIs lose native forms, IME, text selection, find-in-page, autofill, spellcheck, scroll physics, pinch-zoom, print and SEO, and must reimplement them (worse).
3.4 Glass blur: is a custom GPU pass a win?
backdrop-filterlayers over 1920×1080 ≈ 200 ms/frame on mid-tier GPUs; keep 2–3.3.5 Svelte's floor
packages/ui/size-limit.json:"floor: Divider only": 18863B gzip and"root: Button only": 27530B gzip. That entire client runtime is smaller than any wasm UI runtime in the table above.3.6 Answer
Not supported by the evidence for a DOM component library in 2026: a wasm rewrite would be ~10–20% slower at DOM work, 5–25× heavier, lose a11y/forms/SEO parity, and still need JS glue — so not "100% wasm" anyway. A canvas-rendered library can be visually richer and shader-heavy, but it is a different product (app shell, game UI, editor), not a design system with OKLCH tokens,
popover, View Transitions and screen-reader semantics.4. Where wasm and WebGPU do pay off for us
4.1 Decision matrix
color-mix(),oklch()[INFERENCE, measure].wasmdownload + compile[measure]4.2 Three concrete findings in this repo
LiquidGlassrefraction is very likely inert.packages/ui/src/lib/components/LiquidGlass/LiquidGlass.svelte:50:SourceGraphic), not on what is behind it.fill="none"→ transparent source →feDisplacementMapdisplaces nothing →feComposite operator="in"keeps nothing.[INFERENCE — verify with VRT]The visible glass is entirely the CSSbackdrop-filter+ tint + rims, and 4 of 6 optics (depth,curvature,sheen,dispersion) are unused. Fix:backdrop-filter: url(#id)behind@supports(Chromium only), mapping the unused optics to CSS, or delete the overlay.packages/ui/src/lib/utils/highlighter.ts:8callscreateHighlighterfrom"shiki"(default engine = Oniguruma compiled to wasm). Switching toshiki/core+createJavaScriptRegexEngineremoves a wasm download; shiki's docs say all built-in languages are supported since 3.9.1. This is a guaranteed byte win, by removing wasm.DataTable.sveltesimple mode sorts withString(...).localeCompare(...)per compare and filters withString(v).toLowerCase().includes(q)per keystroke, with no virtualization. For 100 k rows that is ~1.7 M string allocations per sort. Fix in JS: precomputed sort keys, a reusedIntl.Collator, sorting aUint32Arrayindex, a pre-lowercased search blob per row. No wasm.4.3 Admission thresholds for wasm (all must hold)
SharedArrayBuffer.4.4 Toolchain, if something ever passes
Rust +
wasm-bindgen --target web(plain ESM +.wasmURL, bundler-agnostic) +wasm-opt -Oz; wasm-pack adds npm packaging noise thatsvelte-packagedoes not need. Floor ≈ 7–10 KB gzip per Rust module once it allocates. Loading:new URL(…, import.meta.url)is the only asset reference every consumer bundler understands;?init/?urlare Vite-only.scripts/size-limit.tsgets a newassetentry kind (gzip of a raw file) under the same ratchet.5. The motion runtime that actually reaches that level
5.1 What runs on the compositor
Chromium's
kCompositableProperties(compositor_animations.cc):backdrop-filter, filter, opacity, rotate, scale, transform, translate, plusbackground-colorandclip-paththrough native paint worklets.transform,translate,rotate,scaleopacityfilterbackdrop-filteropacity, not its blurclip-pathbackground-colorwidth/height/inset/margin/padding/gap@property+ compositable consumer [INFERENCE]Design rule: every
ca-motion-*utility is expressible withtransform/opacity(+filter, +clip-pathfor reveals). Layout properties only as CSS transitions on discrete state changes.5.2 Drivers
transition@keyframes@starting-styledisplay:none → block(Chrome 117, Firefox 129, Safari 17.5)element.animate()transition:/animate:animation-timelinesvelte/motionSpring/Tweenmotionminianimate5.3 Verified Svelte 5.57.2 facts
transition:/in:/out:compile to WAAPI.node_modules/svelte/src/internal/client/dom/elements/transitions.jsL441–447 pre-samples keyframes once (n = Math.ceil(duration / (1000/60)), 240 ms → 15 samples) and then callselement.animate(keyframes, { duration, fill: 'forwards' }).tickruns a rAFloop()on the main thread.css-only transitions have zero per-frame JS. Never passtick.animate:flipuses the same WAAPI path withtranslate/scaleonly.slideanimates height/padding/margin → layout per frame.svelte/motionis main-thread rAF:node_modules/svelte/src/motion/spring.jsL248–274 (this.#task ??= loop(...)→tick_spring→set()→ reactive flush).packages/ui/src/lib/components/AppShell/AppShellLayout.svelte:135:createPresence(primitives/presence.svelte.ts) already gates unmount onel.getAnimations({ subtree: true }), so it works with CSS and WAAPI alike and collapses instantly when durations are zero.5.4 Springs as
linear()easingspackages/ui/src/lib/styles/tokens.css:77today:--ca-ease-spring: cubic-bezier(0.175, 0.885, 0.32, 1.275);— an overshoot, not a spring. CSSlinear()(Chrome 113, Firefox 112, Safari 17.2) can encode a sampled damped oscillator aslinear(0, 0.013, 0.05, …, 1.02, 1.008, 1), generated with Jake Archibald's generator. Zero JS per frame. Fallback order: keep the cubic-bezier first, override inside@supports (animation-timing-function: linear(0, 1)). Alinear()spring has a fixed settle time, so duration and easing ship as one token:--ca-spring-snappy: 380ms linear(...). 5 presets ≈ 2–3 kB raw, ~0.8 kB gzip[INFERENCE]; they go in a newmotion.csswith its own size-limit entry, not incore.css.5.5 Platform primitives that remove JS
popover+::backdrop@starting-styletransition-behavior: allow-discretedisplaynot transitionable → keepcreatePresenceinterpolate-size/calc-size()height: autogrid-template-rows: 0fr → 1frfield-sizing: content@propertyanimation-timelineHoudini:
@property→ use (Baseline). Paint API → Chromium-only, decoration-only progressive enhancement. Animation Worklet → dead (origin trial Chrome 71–74 only). Layout Worklet → flag only.5.6 Libraries
motion(minianimate)animate18–22 kB)@rive-app/canvasZero new runtime deps in core. Svelte's own
element.animatepath + CSS covers the catalog. Rive/Lottie are content formats, never in root entries.5.7 Reduced motion, encoded by class
AuthLayoutblobs loop at 9 s / 12 s / 15 s and need a pause control or removal under reduced motion.--ca-motion-scale: 1 → 0zeroes every duration. Proposal: three axes per token —kind(fade/move/morph/loop),distance(xs 4px … xl 64px),duration— plus--ca-motion-distance-scale: 0under reduced motion, so movement collapses while a 120 ms opacity fade survives. A user-level[data-ca-motion="reduced"|"full"]override on<html>.5.8 Architecture
Tokens (extend, don't rename):
Utilities (data-state driven, no JS):
Attachments (
{@attach}), all WAAPI, reactive, matching the repo's existing{@attach presence.ref}convention:motion(() => ({ preset, when })),reveal()(IntersectionObserver fallback),flipGroup()(FLIP for arbitrary containers),viewTransition(fn)(document.startViewTransition?.(fn) ?? fn(), skipped under reduced motion).5.9 WAAPI vs CSS
transition@starting-style+transitionanimationon[data-state=closed]+createPresenceanimate:flip(WAAPI) orviewTransition()@supports; IO fallbacksvelte/motionSpring→ translate onlytransition: width var(--ca-spring-snappy)— removes one rAF loop + per-frame layoutanimation, paused under reduced motion6.
ca-prompt: a numbered catalog generated from the 236 existing demos6.1 What we already have
site/src/demos/<group>/<component>/<Variant>.svelte— 238.sveltefiles in 9 groups (charts, display, docs-kit, feedback, foundation, inputs, layout, navigation, surfaces): 236 demos + 2 helpers (foundation/colors/Swatches.svelte,foundation/typography/TypeRows.svelte); 236<Demousages in 89 MDX filespackages/ui/scripts/build-manifest.ts→dist/components.json,tokens.json,guides.jsonpackages/ui/bin/ca-ui-mcp.ts—list_components, get_component, get_export, search_tokens, get_guide.agents/skills/codeatlas-ui/tokens.css—--ca-duration-{press,fast,base,slow},--ca-ease-{standard,decelerate,accelerate,emphasized,spring},--ca-motion-scale; already richer than Lucian's--ux-ease/--ux-springSo ~80% of the plumbing exists. What is missing:
<Demo>props onlycomponents.json/tokens.json(API-level)api/v1/per demosearch_demos,get_demo.claude-plugin/*Note: our demos import
@codeatlasdev/uiby design — the "kit" is the library itself.ca-promptis a usage catalog, not a copy-paste catalog, and that is the right product for a component library.6.2 Proposal
site/src/demos/catalog.ts, checked in, append-only:[{ id: "001", path: "inputs/button/States" }, …]. The build fails if a demo has no id or an id's path disappeared.<Variant>.meta.json:{ title, summary, use, spec[], tags[], shape }, withspecwritten from the demo's literal code values (motion-prompt's rule).buildDemos()inbuild-manifest.tsemitsdist/demos.json(ships in the tarball) andsite/public/api/v1/{catalog.json, en/catalog.tsv, en/e/NNN.txt, en/p/NNN.txt}. Everye/NNN.txtstarts with:<!-- @codeatlasdev/ui demo #NNN · MIT License (c) CodeAtlas · <url> -->followed by a header (
Element,What it shows,Spec,Tags,Needs: @codeatlasdev/ui@<ver>,Components,Live demo) and the raw.sveltesource.search_demos(query, group?)andget_demo(id | "#012" | url), capped at 8 ids per call.?pick=, pasted picks, ≤ 8get_demo, port rules, report line).codeatlas-ui pick: opens the docs site with a token, listens on 127.0.0.1, prints picks;?pick=012,045links on the site. Reference: motion-prompt implements?pick=and thepicklistener inassets/site.js:594-598,698andmotion-prompt.mjs:431-433, documented in its SKILL.md (not FORMAT.md)..claude-plugin/{plugin.json, marketplace.json}at the repo root.6.3 Prompt template (
p/NNN.txt)6.5 Showcase
A
/gallerypage on the Astro site lists every numbered demo as a live card (lazy, mounted when visible), filterable by group and tag. Each card has a Pick toggle; a sticky bar offers Copy picks (textI picked these N from the @codeatlasdev/ui gallery: #012 …) and Send to AI (navigates to the?cb=callback when present, motion-prompt's model). Deep links:/gallery#012scrolls to a card,?pick=012,045preselects.6.4 Effort
site/src/demos/catalog.ts,build-manifest.ts*.meta.jsonbuildDemos()+api/v1emitbuild-manifest.tsca-ui-mcp.ts,SKILL.md,DEMOS.mdcodeatlas-ui.ts,Demo.astro.claude-plugin/*7. Roadmap
Each phase becomes issues labeled
area:motion/area:perf/area:docs/enhancement, one worktree per issue per the AGENTS.md workflow.The former P0 (Measure) was dropped: wasm is already known to be slower for DOM work; no in-house wasm is planned, so no harness.
P1 — Motion runtime (depends on nothing)
motion.css+ size-limit entry;--ca-move-*,linear()spring tokens,@property. No component changes.--ca-ease-springusages:AccordionItem.svelte:133,Collapsible.svelte:125,Switch.svelte:131,SegmentedControl.svelte:120.grid-template-rows: 0fr → 1fr+@starting-style; keepcreatePresencefor exit.popover+ anchor positioning behind@supports, JS positioning as fallback.AppShellLayoutpanel width:Spring→ CSS transition.motion / reveal / flipGroup / viewTransition+rise / poptransitions.motion.cssholds.P2 — Byte wins and fixes (independent)
shiki/core+createJavaScriptRegexEngine.Intl.Collator, index sort, lowercase blob, virtualization.backdrop-filter: url()behind@supportsor delete the overlay and the 4 dead optics.P3 —
ca-promptcatalog (depends on P1 for the motion demos' spec lines)buildDemos()→dist/demos.json+site/public/api/v1; MCPsearch_demos/get_demo; skill section "Pick a demo by number" +.claude-pluginmanifests;/galleryshowcase (§6.5).codeatlas-ui pickCLI (127.0.0.1 token listener).get_demo("#012")returns the license line, spec and source; the build fails on an unregistered demo.P4 — Surface engines (glass is one engine, not the identity)
styles/surface.cssalready paints every surface from--ca-surface-*withmaterial="auto"followinghtml[data-style](recipes solid/frosted/flat/outlined/neumorphic inmaterials.css:371-472;materials.css:360says default = solid, glass opt-in).backdrop-filterlocally..ca-surface/material="auto"; alint:tokensrule rejectingbackdrop-filteroutsidestyles/surface.cssandstyles/materials.css; MCP toollist_surfacesgenerated from the[data-style="…"]selectors inmaterials.css; per-platformdefaultStyledocumented for Tauri WebKitGTK[INFERENCE: validate with feature detection].P5 — SEO/PWA excellent by default
seo/shipsSeo,renderOgImage/ogImageRoute/ogCard, JSON-LD builders,sitemapXml,robotsTxt,llmsTxt,markdownNegotiation,manifest,appleMeta,iconLinks;initwires none of it. No service worker, offline route or install prompt in code (docs only,getting-started/pwa.mdx). The brand manifest (brand.ts:208-214) isdisplay:"browser"with light colors only, whilemanifest()defaults tostandalone.initscaffoldsmanifest.webmanifest/+server.ts,robots.txt/+server.ts,sitemap.xml/+server.ts,src/service-worker/index.ts(precache shell + offline route via$app/service-worker) and<Seo>in the root layout;generateBrandemitsdisplay:"standalone"withdefaultSchemecolors and a manifestid;Seogains a default OG image,twitter:title/descriptionandlang; anInstallPromptcomponent (Chromiumbeforeinstallprompt, iOS instructions fallback); Lighthouse CI (SEO/PWA/perf) invalidate.P6 — Native-feeling navigation
data-sveltekit-preload-datahover|tap (template default hover) andpreload-codeeager|viewport|hover|tap, disabled with"false";navigatingfrom$app/state;onNavigate+startViewTransition; server-load promise streaming. Kit has noloadingfile; Svelte's<svelte:boundary>pendingsnippet fills that role (SSR renders pending). NN/g: nothing under 1 s, skeleton for full-page loads < 10 s, spinner for a single module, percent bar ≥ 10 s.<LoadingBoundary delay=200 min=500>wrapping<svelte:boundary>withpendingandaria-busy; askeletonprop on data components (SimpleTable, KpiCard, Avatar, TextField, GlassCard) rendering same-size placeholders (CLS 0);<NavigationProgress>readingnavigating; adata-pendingattachment per link;viewTransition()from P1; AppShell test "zero remounts across 10 navigations";Skeleton.svelteshimmer moves frombackground-position(:86-89, repaint per frame) to atranslatepseudo-element; docs page "App navigation" with preload defaults (<body data-sveltekit-preload-data="hover">,preload-code="viewport"on nav links).P7 —
@codeatlasdev/ui/fxislands<canvas>behind an attachment,role="img"with a label oraria-hiddenwhen decorative, paused offscreen and onvisibilitychange, WebGPU with WebGL2 fallback.LiquidBackdropover self-rendered content, particle/physics fields, optional Rive wrapper lazy-loadingcanvas-lite(222 KB brotli).8. Open questions for the community
@codeatlasdev/ui/rive), or is a docs recipe enough?meta.lang, with English as the source language?Sources
Prompt packs
Grand-V / WebAssembly platform
Wasm UI frameworks and canvas engines
Glass / blur
Practical wasm toolchain
Motion runtime
All reactions