diff --git a/web/.design-sync/.gitignore b/web/.design-sync/.gitignore
new file mode 100644
index 00000000..6a4c5642
--- /dev/null
+++ b/web/.design-sync/.gitignore
@@ -0,0 +1,3 @@
+.cache/
+learnings/
+node_modules
diff --git a/web/.design-sync/NOTES.md b/web/.design-sync/NOTES.md
new file mode 100644
index 00000000..7e9ff5dc
--- /dev/null
+++ b/web/.design-sync/NOTES.md
@@ -0,0 +1,72 @@
+# design-sync notes — Web Sequence (Drafting Table)
+
+Repo-specific gotchas for syncing `web/src/ui` (the "Drafting Table" design system)
+to the "Web Sequence Design System" claude.ai/design project.
+
+## Shape & build
+- **Package shape, synth-entry.** `web/` is a Vite *app*, not a published library — no
+ `main`/`module`/`exports`, no library `dist/` with `.d.ts`. The converter synthesizes the
+ entry from `srcDir: src/ui` (the barrel `src/ui/index.ts`). Run `package-build.mjs` WITHOUT
+ `--entry`. `--node-modules ./node_modules` (web/'s own; react/radix resolve there).
+- React 19 + Radix UI primitives + Tailwind 3. `@types/react` must be installed in `.ds-sync`.
+
+## CSS (Tailwind) — must be recompiled on re-sync
+- Component classes are Tailwind utilities; `tailwind.config.js` is the source of truth. The
+ converter scrapes a *static* stylesheet, so we precompile one into `cssEntry`:
+ ```sh
+ cd web
+ npx tailwindcss -i src/styles/globals.css -o .design-sync/.cache/ds-tailwind.css
+ # then prepend the Google-Fonts @import (brand fonts load at runtime, like index.html):
+ ```
+ The `.design-sync/build-css.sh` helper does both. Re-run it before every build so new
+ utility classes used by authored previews are included (extend its content glob to
+ `.design-sync/previews` once previews exist).
+- **Fonts: Google Fonts at runtime** (Hanken Grotesk / IBM Plex Mono / Instrument Serif), via
+ a `` in `index.html`. Not shipped as woff2. We inject the same `@import url(fonts.googleapis…)`
+ at the top of the compiled CSS → `[FONT_REMOTE]` (loads at runtime, no woff2 to ship).
+ `runtimeFontPrefixes` is set as a backstop so `[FONT_MISSING]` stays quiet.
+
+## Preview authoring — calibration learnings (solo: Button/IconButton/Dialog)
+- **Dark-surface DS → wrap preview content in a dark ink panel** (`background:#10141B`, padding,
+ radius). The grading capture sheet uses a WHITE bg, so `surface="dark"` controls (muted
+ neutrals: ondark-muted icons, ghost buttons) render as faint gray and grade poorly unless
+ they sit on the ink panel they're designed for. `surface="light"` variants get a warm paper
+ panel (`#FAF7F1`). Every dark-surface preview should follow this.
+- Previews import from `'web-sequence-web'` (the converter aliases it to `window.DraftingTable`).
+ esbuild auto-JSX runtime — no `import React` needed; `React.FC`/`React.CSSProperties` type
+ annotations are fine (erased), the IDE's "Cannot find React" warnings are noise.
+- Overlay components (Dialog, and likely Popover/Tooltip/Menu/Select open states) need
+ `cfg.overrides.: {"cardMode":"single","viewport":"WxH"}` and render open via Radix
+ `defaultOpen`/`open` so the floated content shows inside the card.
+- Use realistic copy from the app (Save changes / Delete diagram / Run diagram), never foo/bar.
+
+## Per-component authoring notes (folded from the wave fan-out)
+- **TextInput / Textarea**: thin native wrappers; `surface` prop (default dark) + native attrs.
+ Uncontrolled `defaultValue` works in previews (onChange is the raw DOM event).
+- **SearchInput**: NOT a thin wrapper — `onChange(value: string)` is REQUIRED and value-style
+ (string, not event); `value` is controlled. A static `value="…"` + a no-op `onChange` renders
+ the populated state (with the clear ×). `style`/rest spread onto the inner ``.
+- **Switch**: on/off via `defaultChecked` (checked = cobalt fill); compose as labelled rows
+ (mirrors SettingsModal's SwitchRow) on the ink panel.
+- **Select / Menu / Popover** (overlays): render OPEN via Radix `defaultOpen` on the Root; the
+ orchestrator-set `cfg.overrides.{…}` (cardMode single + viewport) lands the portaled content
+ in the card. `SelectContent`/`MenuContent` are dark-ink; `PopoverContent` is light-paper (it
+ brings its own surface even off a dark trigger). `MenuItem tone="danger"` = red Delete.
+- **Tooltip** (the context-identity trap): the DS `Tooltip` wrapper self-provides a Radix Root
+ with NO open-control prop, and the barrel exports only `Tooltip`+`TooltipProvider` (no raw
+ Root/Trigger/Content). To show it open, the preview imports raw `@radix-ui/react-tooltip` AND
+ supplies its OWN `` — the bundle's `cfg.provider` TooltipProvider is a
+ DIFFERENT module copy with a distinct React context, so a raw Root can't see it (renders blank
+ + throws "must be used within TooltipProvider"). **General rule**: any overlay whose package
+ wrapper self-provides but exposes no open prop → preview supplies its own matching Radix Provider.
+
+## Known render warns (re-sync should treat these as expected, not new)
+- `[RENDER_THIN]` on **BrandLogo** — false positive: the logo is a pure SVG with no text nodes.
+ Grade `good` whenever the mark renders.
+
+## Re-sync risks
+- `.design-sync/.cache/ds-tailwind.css` is gitignored and regenerated — re-sync MUST run
+ `build-css.sh` first or the bundle ships unstyled.
+- Compound exports (DialogTrigger, SelectItem, MenuItem…) are PascalCase and may be discovered
+ as separate components — they're real API parts, not standalone cards; previews compose them
+ inside their parent (Dialog, Select, Menu).
diff --git a/web/.design-sync/build-css.sh b/web/.design-sync/build-css.sh
new file mode 100755
index 00000000..f0c7bd3b
--- /dev/null
+++ b/web/.design-sync/build-css.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+# Compile the Drafting Table Tailwind CSS into the design-sync bundle's cssEntry.
+# Content covers src/** (the DS + app usage) AND authored previews.
+set -e
+cd "$(dirname "$0")/.." # -> web/
+OUT=.design-sync/.cache/ds-tailwind.css
+mkdir -p .design-sync/.cache
+npx tailwindcss -i src/styles/globals.css -o "$OUT" \
+ --content './index.html,./src/**/*.{ts,tsx},./.design-sync/previews/**/*.{ts,tsx}'
+FONT="@import url('https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&family=Instrument+Serif:ital@0;1&display=swap');"
+{ echo "$FONT"; cat "$OUT"; } > "$OUT.tmp" && mv "$OUT.tmp" "$OUT"
+echo "wrote $OUT ($(wc -c <"$OUT") bytes)"
diff --git a/web/.design-sync/config.json b/web/.design-sync/config.json
new file mode 100644
index 00000000..1153c0cd
--- /dev/null
+++ b/web/.design-sync/config.json
@@ -0,0 +1,34 @@
+{
+ "projectId": "ffa534fb-665d-47a6-af0d-7dbbc27d5e88",
+ "shape": "package",
+ "pkg": "web-sequence-web",
+ "globalName": "DraftingTable",
+ "srcDir": "src/ui",
+ "tsconfig": "tsconfig.json",
+ "cssEntry": ".design-sync/.cache/ds-tailwind.css",
+ "readmeHeader": ".design-sync/conventions.md",
+ "runtimeFontPrefixes": ["Hanken Grotesk", "IBM Plex Mono", "Instrument Serif"],
+ "componentSrcMap": {
+ "Button": "src/ui/Button.tsx",
+ "IconButton": "src/ui/IconButton.tsx",
+ "BrandLogo": "src/ui/BrandLogo.tsx",
+ "Dialog": "src/ui/Dialog.tsx",
+ "TextInput": "src/ui/TextInput.tsx",
+ "Textarea": "src/ui/Textarea.tsx",
+ "Switch": "src/ui/Switch.tsx",
+ "Select": "src/ui/Select.tsx",
+ "SearchInput": "src/ui/SearchInput.tsx",
+ "Popover": "src/ui/Popover.tsx",
+ "Tooltip": "src/ui/Tooltip.tsx",
+ "Menu": "src/ui/Menu.tsx"
+ },
+ "provider": { "component": "TooltipProvider" },
+ "overrides": {
+ "Dialog": { "cardMode": "single", "viewport": "480x360" },
+ "Select": { "cardMode": "single", "viewport": "360x320" },
+ "Popover": { "cardMode": "single", "viewport": "400x300" },
+ "Menu": { "cardMode": "single", "viewport": "360x340" },
+ "Tooltip": { "cardMode": "single", "viewport": "360x200" },
+ "Textarea": { "cardMode": "column" }
+ }
+}
diff --git a/web/.design-sync/conventions.md b/web/.design-sync/conventions.md
new file mode 100644
index 00000000..d5fcb7b3
--- /dev/null
+++ b/web/.design-sync/conventions.md
@@ -0,0 +1,57 @@
+# Drafting Table — how to build with this design system
+
+The ZenUML Web Sequence UI kit. Compose from the real components on
+`window.DraftingTable.*` (Button, IconButton, Dialog, TextInput, Textarea, Switch,
+Select, SearchInput, Popover, Tooltip, Menu, BrandLogo) and style your own layout
+glue with the Tailwind utilities below. Two fixed surfaces, one cobalt accent — never
+a generic gray/blue palette.
+
+## Setup
+- Load `styles.css` — it `@import`s the tokens, the component CSS (`_ds_bundle.css`),
+ and the brand fonts (Hanken Grotesk / IBM Plex Mono / Instrument Serif from Google
+ Fonts). Without it everything renders unstyled in a fallback font.
+- **No global wrapper is needed** for most components — they style themselves. The one
+ exception: anything using `Tooltip` must be inside `` (export it from
+ `window.DraftingTable.TooltipProvider`, wrap once near the root).
+
+## The two surfaces (the core idea)
+Interactive components are **surface-aware** via a `surface` prop, not a runtime theme:
+- `surface="dark"` (the **default**) — the **ink** chrome: charcoal-with-blue-undertone
+ editor/header/toolbar/menus. Most of the app lives here.
+- `surface="light"` — the warm **paper** surface (the diagram canvas / some popovers).
+Pick the surface to match the panel you place the component on. `Button`, `IconButton`,
+`TextInput`, `Textarea`, `SearchInput` all take `surface`; `SelectContent`/`MenuContent`
+are dark ink, `PopoverContent` is light paper by design.
+
+## Styling idiom — semantic Tailwind utilities
+Utility classes from a custom palette (NOT Tailwind's default gray/blue). Use real names:
+
+| role | classes |
+|---|---|
+| Dark surfaces (ink) | `bg-ink-950` (backdrop) · `bg-ink-900` (rail) · `bg-ink-800` (panel) · `bg-ink-700` (raised) · `border-ink-line` |
+| Light surfaces (paper) | `bg-paper-50` · `bg-paper-100` · `bg-paper-200` · `border-paper-line` |
+| Accent — the one cobalt signal | `bg-accent` · `bg-accent-press` (pressed) · `text-accent` (fills/rings); for accent TEXT on dark use `text-accent-onDark` (AA-safe) |
+| Text on dark | `text-ondark-strong` · `text-ondark-muted` · `text-ondark-faint` |
+| Text on light | `text-onlight-strong` · `text-onlight-muted` · `text-onlight-faint` |
+| Intent | `text-danger` (use `text-danger-strong` for danger TEXT on light) · `text-ok` · `text-signal-amber` (sparingly) |
+| Type | `font-sans` (Hanken Grotesk, default) · `font-mono` (IBM Plex Mono, code/DSL) · `font-serif` (Instrument Serif, display titles like the Dialog heading) |
+| Shape | `rounded` (7px) · `rounded-lg` (11px) · `shadow-pop` / `shadow-pop-dark` for lifted surfaces |
+
+Rules of thumb: dark panels pair `bg-ink-*` with `text-ondark-*`; light panels pair
+`bg-paper-*` with `text-onlight-*`. Reach for `bg-accent` once per view, not everywhere.
+
+## Where the real truth lives
+- `styles.css` and its `@import` closure (`_ds_bundle.css`, tokens) — the compiled palette.
+- Each component's `components///.d.ts` (its prop API) and
+ `.prompt.md` (usage). Read those before composing a component you're unsure of.
+
+## One idiomatic snippet
+```tsx
+// A dark toolbar with the primary action + an icon control, on the ink surface.
+
+
+
+
+
+```
+`variant` on Button: `primary` (cobalt) · `subtle` · `ghost` · `danger`. Sizes `sm` / `md`.
diff --git a/web/.design-sync/previews/BrandLogo.tsx b/web/.design-sync/previews/BrandLogo.tsx
new file mode 100644
index 00000000..643166a4
--- /dev/null
+++ b/web/.design-sync/previews/BrandLogo.tsx
@@ -0,0 +1,30 @@
+import { BrandLogo } from 'web-sequence-web';
+
+// The official ZenUML mark — a self-contained #2E94D4 rounded-square SVG that
+// carries its own background, so it reads on any surface. Sized via a wrapping
+// div width (the SVG fills 100% of its box at a 300×300 viewBox).
+// NOTE: BrandLogo trips a [RENDER_THIN] warn — it's an SVG with no text nodes,
+// which is a FALSE POSITIVE for a logo. Grade `good` whenever the mark renders.
+
+// The logo at the three sizes the app uses it: brand wordmark (48), header /
+// menu avatar (30), and a compact favicon-scale chip (20) — on neutral paper.
+export function Sizes() {
+ return (
+
+
+
+
+
+ );
+}
+
+// In context: the mark sitting in the dark app header next to the product name,
+// exactly as AppMenu / HomeView place it (className-sized to 30×30).
+export function InHeader() {
+ return (
+
+
+ ZenUML
+
+ );
+}
diff --git a/web/.design-sync/previews/Button.tsx b/web/.design-sync/previews/Button.tsx
new file mode 100644
index 00000000..c08c713e
--- /dev/null
+++ b/web/.design-sync/previews/Button.tsx
@@ -0,0 +1,51 @@
+import { Button } from 'web-sequence-web';
+
+// Drafting Table is a dark-surface DS: `dark` (default) controls live on the ink
+// chrome. Preview them on that ink panel so the quiet variants (ghost/subtle) read
+// with proper contrast; the `light` variant gets the warm paper panel.
+const Ink: React.FC<{ children: React.ReactNode }> = ({ children }) => (
+
+ {children}
+
+);
+
+// Intent-based variants on the dark `ink` chrome (header/toolbar surface).
+export function Variants() {
+ return (
+
+
+
+
+
+
+ );
+}
+
+// Two sizes for toolbars (sm) vs. dialogs/forms (md).
+export function Sizes() {
+ return (
+
+
+
+
+
+
+ );
+}
+
+// Disabled state (dark) + the light-paper surface variant (used inside modals/menus).
+export function StatesAndSurface() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/web/.design-sync/previews/Dialog.tsx b/web/.design-sync/previews/Dialog.tsx
new file mode 100644
index 00000000..10005ead
--- /dev/null
+++ b/web/.design-sync/previews/Dialog.tsx
@@ -0,0 +1,20 @@
+import { Dialog, DialogContent, Button } from 'web-sequence-web';
+
+// Modal shell over Radix Dialog on the dark ink surface. Rendered open (`defaultOpen`)
+// so the card shows the lifted dialog; compose DialogContent with a title, optional
+// description, and your own footer actions. (Ported from the app's ConfirmDialog.)
+export function ConfirmDestructive() {
+ return (
+
+ );
+}
diff --git a/web/.design-sync/previews/IconButton.tsx b/web/.design-sync/previews/IconButton.tsx
new file mode 100644
index 00000000..3a20859e
--- /dev/null
+++ b/web/.design-sync/previews/IconButton.tsx
@@ -0,0 +1,56 @@
+import { IconButton } from 'web-sequence-web';
+
+// Drafting Table is a dark-surface DS: its `dark` controls are muted neutrals meant
+// to sit on the ink chrome. Preview them on that ink panel (else the icons read as
+// faint gray on a white card). The `light` variant gets the warm paper panel.
+const Ink: React.FC<{ children: React.ReactNode }> = ({ children }) => (
+
+);
+
+const Close = () => (
+
+);
+const Plus = () => (
+
+);
+
+// Icon-only controls for toolbars and tab affordances. aria-label is required.
+export function Toolbar() {
+ return (
+
+
+
+
+ );
+}
+
+export function Sizes() {
+ return (
+
+
+
+
+ );
+}
+
+// The light-paper surface variant + a disabled control.
+export function SurfaceAndDisabled() {
+ return (
+
+
+
+
+
+ );
+}
diff --git a/web/.design-sync/previews/Menu.tsx b/web/.design-sync/previews/Menu.tsx
new file mode 100644
index 00000000..0fe27ad6
--- /dev/null
+++ b/web/.design-sync/previews/Menu.tsx
@@ -0,0 +1,54 @@
+import {
+ Menu,
+ MenuTrigger,
+ MenuContent,
+ MenuItem,
+ MenuLabel,
+ MenuSeparator,
+} from 'web-sequence-web';
+
+// Design-system dropdown Menu over Radix DropdownMenu. Menus are DARK (ink) — they
+// drop from the dark chrome (header, diagram-card kebab) and bring their own surface.
+// Rendered OPEN (`defaultOpen`) so the card shows the floated menu items, not just
+// the trigger. Content + items copied from the DiagramCard kebab menu (Duplicate /
+// Export / a separator / a destructive Delete), the canonical realistic item list.
+const inkPanel: React.CSSProperties = {
+ background: '#10141B',
+ padding: 16,
+ borderRadius: 8,
+};
+
+const trigger: React.CSSProperties = {
+ display: 'inline-flex',
+ alignItems: 'center',
+ gap: 6,
+ height: 32,
+ padding: '0 10px',
+ borderRadius: 6,
+ fontSize: 13,
+ color: '#E7ECF3',
+ background: 'rgba(255,255,255,0.06)',
+ border: '1px solid rgba(255,255,255,0.08)',
+};
+
+export function DiagramActions() {
+ return (
+
+
+
+ );
+}
diff --git a/web/.design-sync/previews/Popover.tsx b/web/.design-sync/previews/Popover.tsx
new file mode 100644
index 00000000..fb09e54e
--- /dev/null
+++ b/web/.design-sync/previews/Popover.tsx
@@ -0,0 +1,77 @@
+import {
+ Popover,
+ PopoverTrigger,
+ PopoverContent,
+ Button,
+} from 'web-sequence-web';
+
+// Design-system Popover over Radix Popover. The trigger sits on the dark ink chrome;
+// PopoverContent floats as a LIGHT paper panel (bg-paper-50) — it brings its own
+// surface, so we still wrap the whole thing in a dark ink panel so the trigger reads.
+// Rendered OPEN (`defaultOpen`) so the card shows the floated content. Body copied
+// from the app's SharePopover (read-only share link + Copy / Stop sharing).
+const inkPanel: React.CSSProperties = {
+ background: '#10141B',
+ padding: 16,
+ borderRadius: 8,
+};
+
+const trigger: React.CSSProperties = {
+ display: 'inline-flex',
+ alignItems: 'center',
+ height: 32,
+ padding: '0 12px',
+ borderRadius: 6,
+ fontSize: 13,
+ color: '#E7ECF3',
+ background: 'rgba(255,255,255,0.06)',
+ border: '1px solid rgba(255,255,255,0.08)',
+};
+
+const field: React.CSSProperties = {
+ flex: 1,
+ height: 32,
+ padding: '0 8px',
+ borderRadius: 6,
+ fontSize: 12,
+ fontFamily: 'IBM Plex Mono, monospace',
+ color: '#2B2B2B',
+ background: '#FFFFFF',
+ border: '1px solid #E3DDD2',
+};
+
+export function ShareLink() {
+ return (
+
+
+
+
+
+
+
+
+ Anyone with this link can view a read-only copy of this diagram.
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/web/.design-sync/previews/SearchInput.tsx b/web/.design-sync/previews/SearchInput.tsx
new file mode 100644
index 00000000..65f111ca
--- /dev/null
+++ b/web/.design-sync/previews/SearchInput.tsx
@@ -0,0 +1,73 @@
+import { SearchInput } from 'web-sequence-web';
+
+// SearchInput = leading magnifier glyph + input + a clear (×) affordance that
+// appears only when there's a value. It's a dark-surface control (home/library
+// toolbars), so the dark cells sit on the ink panel; the `light` variant gets
+// the warm paper panel. `onChange` is value-style and required; the cells pass
+// a fixed value + no-op so the populated state (and its clear button) render
+// statically on the capture sheet.
+const noop = (_: string) => {};
+
+const Ink: React.FC<{ children: React.ReactNode }> = ({ children }) => (
+
+);
+
+// Dark surface, empty: just the placeholder + search glyph (home toolbar).
+export function Placeholder() {
+ return (
+
+
+
+ );
+}
+
+// Dark surface, with a query: clear (×) button now shows on the right.
+export function WithValue() {
+ return (
+
+
+
+ );
+}
+
+// Light (paper) surface: empty placeholder + a populated query with clear button.
+export function Light() {
+ return (
+
+
+
+
+ );
+}
diff --git a/web/.design-sync/previews/Select.tsx b/web/.design-sync/previews/Select.tsx
new file mode 100644
index 00000000..fa910095
--- /dev/null
+++ b/web/.design-sync/previews/Select.tsx
@@ -0,0 +1,36 @@
+import {
+ Select,
+ SelectTrigger,
+ SelectContent,
+ SelectItem,
+ SelectValue,
+} from 'web-sequence-web';
+
+// Design-system Select over Radix Select. The trigger sits on dark ink chrome
+// (the Settings modal context) and the floating SelectContent is a dark ink panel
+// itself. Rendered OPEN (`defaultOpen`) so the card shows the dropdown list, not
+// just the collapsed field. Realistic options copied from SettingsModal's theme row.
+const inkPanel: React.CSSProperties = {
+ background: '#10141B',
+ padding: 16,
+ borderRadius: 8,
+};
+
+export function ThemeSelect() {
+ return (
+
+
+
+ );
+}
diff --git a/web/.design-sync/previews/Switch.tsx b/web/.design-sync/previews/Switch.tsx
new file mode 100644
index 00000000..7fa8bc41
--- /dev/null
+++ b/web/.design-sync/previews/Switch.tsx
@@ -0,0 +1,47 @@
+import { Switch } from 'web-sequence-web';
+
+// Drafting Table is a dark-surface DS: settings toggles live on the ink chrome
+// (the Settings modal sits on the dark surface). Preview the Switch on the ink
+// panel so the paper-200 track + cobalt-accent checked fill read with proper
+// contrast against the panel rather than washing out on the white grading sheet.
+const Ink: React.FC<{ children: React.ReactNode }> = ({ children }) => (
+
+ {children}
+
+);
+
+// A labelled settings row, the way SettingsModal composes Switch (SwitchRow).
+const Row: React.FC<{ label: string; children: React.ReactNode }> = ({ label, children }) => (
+
+ {label}
+ {children}
+
+);
+
+// On vs. off — the two resting states a settings toggle alternates between.
+export function OnAndOff() {
+ return (
+
+
+
+
+
+
+
+
+ );
+}
+
+// Disabled toggles — locked-on and locked-off (e.g. a Pro-gated setting).
+export function Disabled() {
+ return (
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/web/.design-sync/previews/TextInput.tsx b/web/.design-sync/previews/TextInput.tsx
new file mode 100644
index 00000000..0fccafab
--- /dev/null
+++ b/web/.design-sync/previews/TextInput.tsx
@@ -0,0 +1,64 @@
+import { TextInput } from 'web-sequence-web';
+
+// TextInput is a dark-surface DS control by default (lives on the ink chrome —
+// e.g. inline page-tab rename). The grading sheet is white, so dark-surface
+// cells sit on the ink panel; the `light` variant (share URL, settings fields)
+// gets the warm paper panel it's designed for.
+const Ink: React.FC<{ children: React.ReactNode }> = ({ children }) => (
+
+);
+
+const labelDark: React.CSSProperties = { fontSize: 12, color: '#9aa4b2', fontFamily: 'Hanken Grotesk, sans-serif' };
+const labelLight: React.CSSProperties = { fontSize: 12, color: '#6b6356', fontFamily: 'Hanken Grotesk, sans-serif' };
+
+const ACSS_CONFIG = `{
+ "custom": {
+ "1px": "1px solid #d8d4cc"
+ },
+ "breakPoints": {
+ "sm": "@media (min-width: 640px)"
+ }
+}`;
+
+// Dark surface, mono — the Atomizer JSON configuration editor.
+export function DarkCode() {
+ return (
+
+
+
+ );
+}
+
+// Light (paper) surface: empty placeholder vs. filled — the bug-report field.
+export function Light() {
+ return (
+
+
+
+
+ );
+}
+
+// Disabled (light) — submitted, awaiting the GitHub issue redirect.
+export function Disabled() {
+ return (
+
+
+
+ );
+}
diff --git a/web/.design-sync/previews/Tooltip.tsx b/web/.design-sync/previews/Tooltip.tsx
new file mode 100644
index 00000000..950d101c
--- /dev/null
+++ b/web/.design-sync/previews/Tooltip.tsx
@@ -0,0 +1,46 @@
+import { Button } from 'web-sequence-web';
+import * as RadixTooltip from '@radix-ui/react-tooltip';
+
+// Tooltip is an OVERLAY: to grade it we must capture it OPEN. The DS `Tooltip`
+// wrapper (label + children) hardcodes its own Radix Root with no open-control
+// prop, so to render the floated chip statically we compose the raw Radix
+// primitives — Root `defaultOpen`, a DS `Button` trigger, and Content styled
+// VERBATIM with the same classNames the shipped `Tooltip.tsx` Content uses, so
+// what's captured is pixel-identical to the real component's chip.
+//
+// IMPORTANT: we MUST wrap in our OWN `RadixTooltip.Provider` here. The card
+// mount wraps the story in the bundle's `DraftingTable.TooltipProvider`, but
+// that provider comes from the bundle's copy of @radix-ui/react-tooltip, while
+// this preview's `import * as RadixTooltip` is a SEPARATE esbuild-bundled copy
+// with a distinct React context — a raw Root would throw "must be used within
+// TooltipProvider" against the bundle provider. Pairing Provider+Root from the
+// same imported copy keeps the context identity matched.
+//
+// cfg.overrides.Tooltip = { cardMode:"single", viewport:"360x200" } → one card.
+
+const CONTENT_CLASS =
+ 'bg-ink-800 text-ondark-strong border border-ink-line/60 rounded ' +
+ 'px-2 py-1 text-[11px] leading-snug max-w-[220px] ' +
+ 'shadow-pop-dark animate-pop-in z-50 select-none';
+
+// The open explanatory chip below a header control — exactly how RendererHeader
+// wraps the Present button. Rendered open so the floated dark chip + arrow show.
+export function Open() {
+ return (
+
+