Mac prototyping
Web-rendered prototypes that read as native macOS 26/27 apps. The toolkit
lives in this skill directory:
packages/mac-chrome/ — the chrome components + tokens (vendored into
each prototype as lib/mac-chrome/).
template/ — a runnable starter (vinext + React 19 + vitest); a new
prototype is a copy of it.
- references/workflows.md — the command recipes:
new, fork, pick a port, serve durably via launchd, stop, clean up. These
are ordinary
cp/rsync/pnpm/launchctl/curl compositions —
compose and adapt them; there is deliberately no wrapper CLI.
- references/mac-pattern-rubric.md —
the macOS pattern checklist every surface is judged against.
- The
mac-design-audit agent (bundled with this plugin) — pattern-based
design review; convene it as described below.
Edit against a persistent vinext dev service and use HMR as the default
prototype workflow. Keep its private shared URL open while changing source;
ordinary edits do not need a build, restart, or manual refresh. The workflows
also define the built-preview and service-worker boundaries.
Start or fork a prototype
New = copy template/ to a stable path (~/Prototypes/<name>), rename,
vendor packages/mac-chrome as lib/mac-chrome/, hydrate its local macOS
assets, then pnpm install. Fork = rsync an existing prototype (minus
node_modules/build dirs), rename, install. Exact recipes, the 8600–8699
port convention (lsof is the registry), and the launchd serve/stop recipes are in
references/workflows.md.
Fork instead of complicating: when an idea would fight the current
prototype's complexity, fork it (it's an rsync), give it its own port and
service, and strip what the experiment doesn't need — forking is the
intended way to try directions. Never park a prototype in a per-session
scratch directory; a stable path is what lets services and later sessions
find it.
Each prototype owns its vendored lib/mac-chrome/: edits there refresh in its
development service. Changes to the canonical toolkit require a deliberate
re-vendor into the intended prototype; do not broadly auto-sync library edits
across prototypes.
A surface is two files: a small server app/<surface>/page.tsx that exports
metadata and only delegates, plus a "use client" component beside it that
owns the state and composes MacWindowManager + DesktopShell, one MacApp
per simulated application, their windows (FinderWindow, ChooserWindow,
WindowChrome+MacToolbar, …), and one MacAppDock. Product state and
fixtures live outside lib/mac-chrome/.
For a managed app, define its identity and Dock metadata once as an immutable
MacAppDefinition. Pass the definitions to MacWindowManager through
initialApps and spread the same definition into each MacApp, so SSR already
contains the final app identities and Dock entries before registration effects.
Every new prototype includes two starter routes. Open /showcase first when
discovering components or auditing mac-chrome: it is the interactive coverage
surface for public building blocks and their main compositions. Helpers and
recipe-specific or legacy APIs are exercised indirectly through those stories
or through tests. Keep /example as the small, coherent starter surface for
product work. The template launcher links both routes.
Compose from the standard library
mac-chrome is a composition-first, 80/20 library, not only a collection of
finished demo windows. Start product surfaces with its primitives, then use
the finished windows as recipes when their interaction model fits. The
canonical public contracts are in packages/mac-chrome/README.md; do not
copy a showcase layout or private component into product code.
| Need |
Use |
Native precedent |
| Desktop stage, app menus, status items |
DesktopShell |
menu bar + desktop |
| Windowed or menu-bar app identity, key-window focus, z-order, launch/restore/quit |
MacWindowManager + MacApp (`presentation="windowed" |
"menuBar"`) |
| Managed app launcher and running state |
MacAppDock + MacDockAppIcon (typed DockIcon data) |
Dock tile |
| Standalone decorative Dock |
MacDock |
Dock-like launcher without app lifecycle |
| Two- or three-column navigation |
MacNavigationSplitView |
NavigationSplitView |
| Supplementary metadata or controls |
MacInspector beside the split view |
inspector / preview pane |
| Sectioned sidebar navigation |
MacSourceList |
List(.sidebar) / source list |
| Selectable rows |
MacList |
List |
| Collapsible grouped detail |
MacDisclosureGroup |
DisclosureGroup |
| Buttons, fields, toggles, segmented choices, forms |
MacButton, MacTextField, MacToggle, MacSegmentedControl, MacControlGroup, MacForm, MacFormSection, MacLabeledContent |
standard AppKit / SwiftUI controls |
| No-content state |
MacContentUnavailable |
ContentUnavailableView |
| Window-local status and system decisions |
MacWindowStatusBar, MacAlert, MacSheet |
window status area, .alert, .sheet |
| Commands and anchored choices |
MacMenu, MacDetailsMenu, MacPopover |
NSMenu / NSPopover |
| A complete Finder, chooser, setup flow, or chat window |
FinderWindow, ChooserWindow, SetupAssistant, ChatWindow |
recipes composed above the primitives |
MacNavigationSplitView has either two columns (sidebar + detail) or three
navigation columns (sidebar + content + detail). Its optional middle column
represents a selection hierarchy. MacInspector is deliberately a separate,
supplementary trailing pane; do not treat it as the third navigation column.
When its panel defaults use compatible CSS units, the shared split view
normalizes them for SSR. Use this primitive rather than assembling local panel
layouts, so hydration does not shift its children.
Use the managed app layer for every multi-window desktop. MacApp stays
mounted so closing or minimizing a window does not destroy its product state;
MacAppDock launches, restores, and activates from the same registry.
WindowChrome registers itself with the enclosing app. It owns key-window
focus, click-to-front, traffic lights, contained dragging, ResizeObserver
recontainment, and the default eight-edge resize affordances. Multiple windows
in one app must have distinct stable windowId values; the single-window
default is ${appId}:main. Use resizable and minSize on WindowChrome
instead of recipe-local geometry. Every full-window recipe composes it and
must not override .mac-window positioning. Do not manage product windows by
conditional rendering plus local z-index counters.
Managed minimization is registry-owned: WindowChrome captures its actual
surface, MacWindowManager transitions it to the Dock's separate minimized-
window group, and the thumbnail restores the same window. The app tile remains
running throughout. Do not add a recipe-local minimized card, thumbnail, or
Dock separator. A standalone, unmanaged WindowChrome retains the small local
hide fallback because it has no desktop registry.
Use the default presentation="windowed" for an app with managed windows and
a Dock tile. Use presentation="menuBar" for an app whose visible surface is
a MenuBarExtra; it remains registered but is intentionally omitted from the
Dock. Status feedback belongs in MacWindowStatusBar inside the owning
window. A Dock launch should activate a registered app/window, not write a
description of the Dock item into an unrelated app's status bar. A menu-bar
app uses MacAlert presentationScope="desktop" for a system decision so it
never attaches to an unrelated key window. If the decision originates in a
MenuBarExtra popover, control it with isOpen/onOpenChange and a stable
triggerRef, close it, then open the desktop alert; this lets the shared
modal host clean up background isolation and restore status-trigger focus.
The Dock owns icon normalization. Pass a typed DockIcon where possible:
asset artwork retains its own safe area, while generated symbol artwork is
drawn in MacDockAppIcon's shared 50px canvas and 42px tile. Generated
symbols use a centered 34×30px glyph frame at SystemSymbol size 20; SVG
artwork uses its own selector and stays at most 26×26px. Do not create a local
50px tile, wrap it in a Dock item, or tune one app icon with ad-hoc scale CSS
— that breaks the shared optical-size contract.
The library deliberately does not promise full SwiftUI parity. Tables,
outline views, grid collections, and other specialized patterns stay
out until there is a demonstrated product need. Liquid Glass is explicitly
not a default capability. Use the restrained material tokens rather than
attempting to simulate a system compositor.
Promote a pattern deliberately
Promote repeated product UI into mac-chrome only when it has a real native
counterpart and a reusable contract: documented accessibility and keyboard
behavior, responsive behavior, focused tests, and a working /showcase
example. Prefer evidence from two independent consumers before promotion. A
one-off product layout remains product code; a close-but-not-identical native
pattern is a design question, not an excuse for another local component.
Invariants
These exist because their violations are exactly what made past prototypes
slow to work on (a 9,400-line globals.css with 1,094 hard-coded colors):
- Tokens or nothing. Every color, radius, blur, and shadow goes through
lib/mac-chrome/styles/tokens.css. --accent (system blue) styles
controls and focus; --brand (product color) styles identity only.
- One CSS file per component/surface. Never a shared growing global
stylesheet. Do not add
backdrop-filter recipes to the default chrome.
- The chrome package never imports product code. Product → chrome only.
- Icons come in three tiers. SF-style glyphs:
SystemSymbol, backed by
typed symbolist private-use codepoints and the macOS system SF font at
render time. The template's SFSymbol is a deprecated compatibility alias;
new chrome code imports SystemSymbol. Do not draw or ship bespoke SVG
approximations of SF Symbols. SystemSymbol renders its glyph directly
with intrinsic variable-width font metrics: it does no runtime measurement,
observation, scaling, translation, or per-symbol offset. Parent components
own fixed, stable icon slots and center the intrinsic glyph with Grid or
Flex. Size font glyphs and SVG artwork in separate selectors because a
font-size is not a square SVG box; choose optical font size by component
role, never by symbol name. Do not add per-icon CSS transforms or offsets.
This path intentionally depends on a Mac client resolving the installed
system font. Third-party brand marks (Drive,
Notion, GitHub…):
simple-icons via the BrandIcon wrapper — committable. Verify the slug
exists — BrandIcon warns in dev on unknown slugs; brands missing from
simple-icons (Slack and Salesforce are absent from v16) fall back to the
private-assets tier. Chrome icon slots render aria-hidden, so BrandIcon
titles carry no accessible name there — test icons by data attribute, not
role. Apple-system lookalikes (Finder/Safari dock icons, wallpapers):
private local assets hydrated at creation time, never committed. Hydration
gets the local system app/folder/trash icons and Tahoe Day wallpaper; its
output stays ignored. No
Apple-owned assets in any repo — no SF Pro font files, no exported SF
Symbol SVGs, no macOS app-icon bitmaps.
- No Unicode stand-ins for system glyphs (
▦ ☷ ⌕ etc.) — SVG or
symbolist only.
- Generated Dock ink stays contained.
MacDockAppIcon remains the shared
50px canvas, 42px tile, and 34×30px glyph-frame boundary; generated
SystemSymbols use size 20 and SVG artwork stays at most 26×26px.
overflow: hidden is only a safety boundary, not the sizing mechanism. Do
not rely on clipping or per-icon adjustments to contain a generated symbol.
- Fidelity before effect. Default chrome to restrained opaque or
near-opaque system materials with a hairline and a subtle system-like
shadow. Do not emulate Liquid Glass or introduce custom
backdrop-filter/saturation recipes unless the owner explicitly asks for
that experiment. Menus and status popovers must remain legible over any
wallpaper.
- One menu/popover system.
MacMenu owns compact 13px/24px command rows;
MacPopover owns arbitrary anchored content with named layout and content
inset choices. MacDetailsMenu composes the latter for an account-style
summary trigger. Do not conflate commands with content popovers or add
bespoke overlays/native <details> controls; they skip the shared
dismissal, focus, and geometry contract.
- One presentation host. Use
MacSheet for an attached task with owned
title/body/actions/insets and MacAlert for a short system decision. Pass
MacDialogAction data (semantic cancel/destructive role plus independent
isDefault) rather than authoring a button row inside a sheet. The legacy
Sheet is compatibility-only; SetupHeading is recipe artwork, not a
general dialog API.
- One disclosure contract. Use
MacDisclosureGroup for grouped detail and
MacSourceList for navigable sidebar sections. The shared indicator is a
SystemSymbol; source-list section headers are structural by default. A
titled section may opt into a controlled selection destination with
selectable: true and selectedSectionId / onSectionSelectionChange;
its disclosure chevron remains a separate action. Do not draw chevrons in
CSS or animate a reveal by scaling it. For MacListSection and
MacDisclosureGroup, accessible labels resolve as explicit ariaLabel,
derived title text, then a compatibility fallback. Supply ariaLabel for
custom, decorative, opaque, or generated titles; generators are normalized
once.
- Chrome earns its controls. Reusable toolbar commands have matching
functional menu commands. The current app appears as a running Dock item;
default windows remain clear of the menu bar and Dock, including at small
viewports. Size explicit frames against the desktop canvas with
%, not
vw/vh; the shell contracts below its 1200px reference width and an
initial window must be wholly visible without horizontal scrolling.
- One app/window lifecycle. A desktop with multiple simulated apps uses
MacWindowManager, MacApp, managed WindowChrome, and MacAppDock.
Click-to-front, key-window state, close/minimize/zoom, Window-menu commands,
minimized-window thumbnails, and Dock restoration must all resolve through
that registry. Define each managed app's immutable MacAppDefinition once;
pass the manifest as initialApps to MacWindowManager and the same
definitions to MacApp so first-render app identity and Dock geometry do
not shift after effects run.
- Compose before styling. Use the shared navigation, source-list, list,
disclosure, form, control, menu, and content-state primitives before
writing a local layout or control. Product CSS may arrange a surface around
those primitives; it must not reimplement their selection, focus,
keyboard, or optical-geometry contracts.
- If a house TypeScript-standards skill is loaded in this environment, it
governs prototype code too; this skill adds prototyping-specific rules,
it does not waive house ones.
Verify like a user, then audit
Develop against the persistent vinext dev service and look at the real
thing after a meaningful batch — screenshot or click the changed flow at
the shared URL. Do not rebuild, restart, or manually refresh for ordinary
edits: rely on HMR. Run targeted checks for the changed behavior, and run
the prototype's complete checks at a completion or checkpoint. Automate
multi-path checks (Playwright/console) instead of hand-stepping. For
component discovery or a broad chrome audit, begin at /showcase before
checking the product surface. Audit symbol geometry immediately on first
render, after the page settles, and after repeated reloads: it must not
change. Include wide symbols such as laptopcomputer and person.2.fill.
First-render HTML/CSS must already contain the final sizing; do not use
post-render measurement, ResizeObserver, transform correction, or
icon-specific translate/scale hacks.
Prove the development loop once for a new or repaired service: through the
actual shared URL, make and restore a reversible CSS edit and a reversible,
compatible component edit; confirm existing React state remains, with no
document reload or console error. React Refresh can reset state for
structural edits. A successful HTTP response alone does not prove HMR.
Serve first and share the URL; reviews run after, not before. Build a
snapshot only when the task calls for one, such as a public demo, a
deliberately stable review, or performance, poor-network, or offline
acceptance.
For direction decisions, new surfaces, or a final pass, create one fresh,
independent reviewer in a context that does not inherit the builder's or
author's conversation, then use the full audit definition.
A general-purpose agent may perform the role only by loading that full
definition and receiving its required inputs; do not substitute a rubric
self-pass. The showcase must
dogfood public primitives, demonstrate both windowed and menu-bar apps, and
cover window containment/focus/resize plus attached and desktop modal
scopes. Exercise both traffic-light and Window-menu minimization: each must
leave the app tile running, create an actual window preview in the Dock's
separate window group, and restore the same window when selected. Resize a
split-view window and shrink/reset the viewport; no error overlay or console
error is acceptable. Do not fix a catalog defect with story-local geometry,
padding, or icon code.
Keep found-issue continuity in the prototype's REVIEW-LEDGER.md, not in
long-lived reviewer conversations — one line per finding
(date · finder · [Pn] finding — file:line → resolution):
2026-08-11 · mac-design-audit · [P1] centered two-line toolbar title — app/files/page.tsx:24 → left-aligned single line
2026-08-11 · owner · [P2] glass on content background — app/files/files-surface.tsx:58 → blur moved to toolbar capsule
2026-08-12 · self · [P3] Unicode ⌕ in search bubble — app/files/files-surface.tsx:71 → SystemSymbol magnifyingglass
When a rule fights you
These rules encode one team's decisions, not physics. If an invariant blocks
the obvious path, or host specifics (ports, launchd, Tailscale) don't match
this machine, say so and ask the owner rather than silently working around
it — and record the resolution in the project's own docs.
1---2name: mac-prototyping3description: Builds native-macOS-style app prototypes on the web using the bundled mac-chrome React/TypeScript toolkit — managed apps and windows, desktop shell, Dock, navigation split views, source lists, forms, controls, menus, window recipes, tokens, and composable new/fork/serve command recipes. Use when creating, forking, changing, or reviewing a macOS-look prototype, or when asked to make a web UI look and behave like a Mac app.4---56# Mac prototyping78Web-rendered prototypes that read as native macOS 26/27 apps. The toolkit9lives in this skill directory:1011- `packages/mac-chrome/` — the chrome components + tokens (vendored into12 each prototype as `lib/mac-chrome/`).13- `template/` — a runnable starter (vinext + React 19 + vitest); a new14 prototype is a copy of it.15- [references/workflows.md](references/workflows.md) — the command recipes:16 new, fork, pick a port, serve durably via launchd, stop, clean up. These17 are ordinary `cp`/`rsync`/`pnpm`/`launchctl`/`curl` compositions —18 compose and adapt them; there is deliberately no wrapper CLI.19- [references/mac-pattern-rubric.md](references/mac-pattern-rubric.md) —20 the macOS pattern checklist every surface is judged against.21- The `mac-design-audit` agent (bundled with this plugin) — pattern-based22 design review; convene it as described below.2324Edit against a persistent `vinext dev` service and use HMR as the default25prototype workflow. Keep its private shared URL open while changing source;26ordinary edits do not need a build, restart, or manual refresh. The workflows27also define the built-preview and service-worker boundaries.2829## Start or fork a prototype3031New = copy `template/` to a stable path (`~/Prototypes/<name>`), rename,32vendor `packages/mac-chrome` as `lib/mac-chrome/`, hydrate its local macOS33assets, then `pnpm install`. Fork = rsync an existing prototype (minus34`node_modules`/build dirs), rename, install. Exact recipes, the 8600–869935port convention (`lsof` is the registry), and the launchd serve/stop recipes are in36[references/workflows.md](references/workflows.md).3738Fork instead of complicating: when an idea would fight the current39prototype's complexity, fork it (it's an rsync), give it its own port and40service, and strip what the experiment doesn't need — forking is the41intended way to try directions. Never park a prototype in a per-session42scratch directory; a stable path is what lets services and later sessions43find it.4445Each prototype owns its vendored `lib/mac-chrome/`: edits there refresh in its46development service. Changes to the canonical toolkit require a deliberate47re-vendor into the intended prototype; do not broadly auto-sync library edits48across prototypes.4950A surface is two files: a small server `app/<surface>/page.tsx` that exports51`metadata` and only delegates, plus a `"use client"` component beside it that52owns the state and composes `MacWindowManager` + `DesktopShell`, one `MacApp`53per simulated application, their windows (`FinderWindow`, `ChooserWindow`,54`WindowChrome`+`MacToolbar`, …), and one `MacAppDock`. Product state and55fixtures live outside `lib/mac-chrome/`.5657For a managed app, define its identity and Dock metadata once as an immutable58`MacAppDefinition`. Pass the definitions to `MacWindowManager` through59`initialApps` and spread the same definition into each `MacApp`, so SSR already60contains the final app identities and Dock entries before registration effects.6162Every new prototype includes two starter routes. Open `/showcase` first when63discovering components or auditing mac-chrome: it is the interactive coverage64surface for public building blocks and their main compositions. Helpers and65recipe-specific or legacy APIs are exercised indirectly through those stories66or through tests. Keep `/example` as the small, coherent starter surface for67product work. The template launcher links both routes.6869## Compose from the standard library7071`mac-chrome` is a composition-first, 80/20 library, not only a collection of72finished demo windows. Start product surfaces with its primitives, then use73the finished windows as recipes when their interaction model fits. The74canonical public contracts are in `packages/mac-chrome/README.md`; do not75copy a showcase layout or private component into product code.7677| Need | Use | Native precedent |78| --- | --- | --- |79| Desktop stage, app menus, status items | `DesktopShell` | menu bar + desktop |80| Windowed or menu-bar app identity, key-window focus, z-order, launch/restore/quit | `MacWindowManager` + `MacApp` (`presentation="windowed" | "menuBar"`) | `NSApplication` + `NSWindow` scene ownership |81| Managed app launcher and running state | `MacAppDock` + `MacDockAppIcon` (typed `DockIcon` data) | Dock tile |82| Standalone decorative Dock | `MacDock` | Dock-like launcher without app lifecycle |83| Two- or three-column navigation | `MacNavigationSplitView` | `NavigationSplitView` |84| Supplementary metadata or controls | `MacInspector` beside the split view | inspector / preview pane |85| Sectioned sidebar navigation | `MacSourceList` | `List(.sidebar)` / source list |86| Selectable rows | `MacList` | `List` |87| Collapsible grouped detail | `MacDisclosureGroup` | `DisclosureGroup` |88| Buttons, fields, toggles, segmented choices, forms | `MacButton`, `MacTextField`, `MacToggle`, `MacSegmentedControl`, `MacControlGroup`, `MacForm`, `MacFormSection`, `MacLabeledContent` | standard AppKit / SwiftUI controls |89| No-content state | `MacContentUnavailable` | `ContentUnavailableView` |90| Window-local status and system decisions | `MacWindowStatusBar`, `MacAlert`, `MacSheet` | window status area, `.alert`, `.sheet` |91| Commands and anchored choices | `MacMenu`, `MacDetailsMenu`, `MacPopover` | `NSMenu` / `NSPopover` |92| A complete Finder, chooser, setup flow, or chat window | `FinderWindow`, `ChooserWindow`, `SetupAssistant`, `ChatWindow` | recipes composed above the primitives |9394`MacNavigationSplitView` has either two columns (sidebar + detail) or three95navigation columns (sidebar + content + detail). Its optional middle column96represents a selection hierarchy. `MacInspector` is deliberately a separate,97supplementary trailing pane; do not treat it as the third navigation column.98When its panel defaults use compatible CSS units, the shared split view99normalizes them for SSR. Use this primitive rather than assembling local panel100layouts, so hydration does not shift its children.101102Use the managed app layer for every multi-window desktop. `MacApp` stays103mounted so closing or minimizing a window does not destroy its product state;104`MacAppDock` launches, restores, and activates from the same registry.105`WindowChrome` registers itself with the enclosing app. It owns key-window106focus, click-to-front, traffic lights, contained dragging, ResizeObserver107recontainment, and the default eight-edge resize affordances. Multiple windows108in one app must have distinct stable `windowId` values; the single-window109default is `${appId}:main`. Use `resizable` and `minSize` on `WindowChrome`110instead of recipe-local geometry. Every full-window recipe composes it and111must not override `.mac-window` positioning. Do not manage product windows by112conditional rendering plus local z-index counters.113114Managed minimization is registry-owned: `WindowChrome` captures its actual115surface, `MacWindowManager` transitions it to the Dock's separate minimized-116window group, and the thumbnail restores the same window. The app tile remains117running throughout. Do not add a recipe-local minimized card, thumbnail, or118Dock separator. A standalone, unmanaged `WindowChrome` retains the small local119hide fallback because it has no desktop registry.120121Use the default `presentation="windowed"` for an app with managed windows and122a Dock tile. Use `presentation="menuBar"` for an app whose visible surface is123a `MenuBarExtra`; it remains registered but is intentionally omitted from the124Dock. Status feedback belongs in `MacWindowStatusBar` inside the owning125window. A Dock launch should activate a registered app/window, not write a126description of the Dock item into an unrelated app's status bar. A menu-bar127app uses `MacAlert presentationScope="desktop"` for a system decision so it128never attaches to an unrelated key window. If the decision originates in a129`MenuBarExtra` popover, control it with `isOpen`/`onOpenChange` and a stable130`triggerRef`, close it, then open the desktop alert; this lets the shared131modal host clean up background isolation and restore status-trigger focus.132133The Dock owns icon normalization. Pass a typed `DockIcon` where possible:134asset artwork retains its own safe area, while generated symbol artwork is135drawn in `MacDockAppIcon`'s shared 50px canvas and 42px tile. Generated136symbols use a centered 34×30px glyph frame at `SystemSymbol` size 20; SVG137artwork uses its own selector and stays at most 26×26px. Do not create a local13850px tile, wrap it in a Dock item, or tune one app icon with ad-hoc scale CSS139— that breaks the shared optical-size contract.140141The library deliberately does not promise full SwiftUI parity. Tables,142outline views, grid collections, and other specialized patterns stay143out until there is a demonstrated product need. Liquid Glass is explicitly144not a default capability. Use the restrained material tokens rather than145attempting to simulate a system compositor.146147### Promote a pattern deliberately148149Promote repeated product UI into `mac-chrome` only when it has a real native150counterpart and a reusable contract: documented accessibility and keyboard151behavior, responsive behavior, focused tests, and a working `/showcase`152example. Prefer evidence from two independent consumers before promotion. A153one-off product layout remains product code; a close-but-not-identical native154pattern is a design question, not an excuse for another local component.155156## Invariants157158These exist because their violations are exactly what made past prototypes159slow to work on (a 9,400-line globals.css with 1,094 hard-coded colors):160161- **Tokens or nothing.** Every color, radius, blur, and shadow goes through162 `lib/mac-chrome/styles/tokens.css`. `--accent` (system blue) styles163 controls and focus; `--brand` (product color) styles identity only.164- **One CSS file per component/surface.** Never a shared growing global165 stylesheet. Do not add `backdrop-filter` recipes to the default chrome.166- **The chrome package never imports product code.** Product → chrome only.167- **Icons come in three tiers.** SF-style glyphs: `SystemSymbol`, backed by168 typed `symbolist` private-use codepoints and the macOS system SF font at169 render time. The template's `SFSymbol` is a deprecated compatibility alias;170 new chrome code imports `SystemSymbol`. Do not draw or ship bespoke SVG171 approximations of SF Symbols. `SystemSymbol` renders its glyph directly172 with intrinsic variable-width font metrics: it does no runtime measurement,173 observation, scaling, translation, or per-symbol offset. Parent components174 own fixed, stable icon slots and center the intrinsic glyph with Grid or175 Flex. Size font glyphs and SVG artwork in separate selectors because a176 `font-size` is not a square SVG box; choose optical font size by component177 role, never by symbol name. Do not add per-icon CSS transforms or offsets.178 This path intentionally depends on a Mac client resolving the installed179 system font. Third-party brand marks (Drive,180 Notion, GitHub…):181 `simple-icons` via the `BrandIcon` wrapper — committable. Verify the slug182 exists — `BrandIcon` warns in dev on unknown slugs; brands missing from183 simple-icons (Slack and Salesforce are absent from v16) fall back to the184 private-assets tier. Chrome icon slots render aria-hidden, so `BrandIcon`185 titles carry no accessible name there — test icons by data attribute, not186 role. Apple-system lookalikes (Finder/Safari dock icons, wallpapers):187 private local assets hydrated at creation time, never committed. Hydration188 gets the local system app/folder/trash icons and Tahoe Day wallpaper; its189 output stays ignored. **No190 Apple-owned assets in any repo** — no SF Pro font files, no exported SF191 Symbol SVGs, no macOS app-icon bitmaps.192- **No Unicode stand-ins for system glyphs** (`▦ ☷ ⌕` etc.) — SVG or193 symbolist only.194- **Generated Dock ink stays contained.** `MacDockAppIcon` remains the shared195 50px canvas, 42px tile, and 34×30px glyph-frame boundary; generated196 `SystemSymbol`s use size 20 and SVG artwork stays at most 26×26px.197 `overflow: hidden` is only a safety boundary, not the sizing mechanism. Do198 not rely on clipping or per-icon adjustments to contain a generated symbol.199- **Fidelity before effect.** Default chrome to restrained opaque or200 near-opaque system materials with a hairline and a subtle system-like201 shadow. Do not emulate Liquid Glass or introduce custom202 `backdrop-filter`/saturation recipes unless the owner explicitly asks for203 that experiment. Menus and status popovers must remain legible over any204 wallpaper.205- **One menu/popover system.** `MacMenu` owns compact 13px/24px command rows;206 `MacPopover` owns arbitrary anchored content with named layout and content207 inset choices. `MacDetailsMenu` composes the latter for an account-style208 summary trigger. Do not conflate commands with content popovers or add209 bespoke overlays/native `<details>` controls; they skip the shared210 dismissal, focus, and geometry contract.211- **One presentation host.** Use `MacSheet` for an attached task with owned212 title/body/actions/insets and `MacAlert` for a short system decision. Pass213 `MacDialogAction` data (semantic `cancel`/`destructive` role plus independent214 `isDefault`) rather than authoring a button row inside a sheet. The legacy215 `Sheet` is compatibility-only; `SetupHeading` is recipe artwork, not a216 general dialog API.217- **One disclosure contract.** Use `MacDisclosureGroup` for grouped detail and218 `MacSourceList` for navigable sidebar sections. The shared indicator is a219 `SystemSymbol`; source-list section headers are structural by default. A220 titled section may opt into a controlled selection destination with221 `selectable: true` and `selectedSectionId` / `onSectionSelectionChange`;222 its disclosure chevron remains a separate action. Do not draw chevrons in223 CSS or animate a reveal by scaling it. For `MacListSection` and224 `MacDisclosureGroup`, accessible labels resolve as explicit `ariaLabel`,225 derived title text, then a compatibility fallback. Supply `ariaLabel` for226 custom, decorative, opaque, or generated titles; generators are normalized227 once.228- **Chrome earns its controls.** Reusable toolbar commands have matching229 functional menu commands. The current app appears as a running Dock item;230 default windows remain clear of the menu bar and Dock, including at small231 viewports. Size explicit frames against the desktop canvas with `%`, not232 `vw`/`vh`; the shell contracts below its 1200px reference width and an233 initial window must be wholly visible without horizontal scrolling.234- **One app/window lifecycle.** A desktop with multiple simulated apps uses235 `MacWindowManager`, `MacApp`, managed `WindowChrome`, and `MacAppDock`.236 Click-to-front, key-window state, close/minimize/zoom, Window-menu commands,237 minimized-window thumbnails, and Dock restoration must all resolve through238 that registry. Define each managed app's immutable `MacAppDefinition` once;239 pass the manifest as `initialApps` to `MacWindowManager` and the same240 definitions to `MacApp` so first-render app identity and Dock geometry do241 not shift after effects run.242- **Compose before styling.** Use the shared navigation, source-list, list,243 disclosure, form, control, menu, and content-state primitives before244 writing a local layout or control. Product CSS may arrange a surface around245 those primitives; it must not reimplement their selection, focus,246 keyboard, or optical-geometry contracts.247- If a house TypeScript-standards skill is loaded in this environment, it248 governs prototype code too; this skill adds prototyping-specific rules,249 it does not waive house ones.250251## Verify like a user, then audit2522531. Develop against the persistent `vinext dev` service and look at the real254 thing after a meaningful batch — screenshot or click the changed flow at255 the shared URL. Do not rebuild, restart, or manually refresh for ordinary256 edits: rely on HMR. Run targeted checks for the changed behavior, and run257 the prototype's complete checks at a completion or checkpoint. Automate258 multi-path checks (Playwright/console) instead of hand-stepping. For259 component discovery or a broad chrome audit, begin at `/showcase` before260 checking the product surface. Audit symbol geometry immediately on first261 render, after the page settles, and after repeated reloads: it must not262 change. Include wide symbols such as `laptopcomputer` and `person.2.fill`.263 First-render HTML/CSS must already contain the final sizing; do not use264 post-render measurement, `ResizeObserver`, transform correction, or265 icon-specific translate/scale hacks.2662. Prove the development loop once for a new or repaired service: through the267 actual shared URL, make and restore a reversible CSS edit and a reversible,268 compatible component edit; confirm existing React state remains, with no269 document reload or console error. React Refresh can reset state for270 structural edits. A successful HTTP response alone does not prove HMR.2713. Serve first and share the URL; reviews run after, not before. Build a272 snapshot only when the task calls for one, such as a public demo, a273 deliberately stable review, or performance, poor-network, or offline274 acceptance.2754. For direction decisions, new surfaces, or a final pass, create one fresh,276 independent reviewer in a context that does not inherit the builder's or277 author's conversation, then use [the full audit definition](agents/mac-design-audit.md).278 A general-purpose agent may perform the role only by loading that full279 definition and receiving its required inputs; do not substitute a rubric280 self-pass. The showcase must281 dogfood public primitives, demonstrate both windowed and menu-bar apps, and282 cover window containment/focus/resize plus attached and desktop modal283 scopes. Exercise both traffic-light and Window-menu minimization: each must284 leave the app tile running, create an actual window preview in the Dock's285 separate window group, and restore the same window when selected. Resize a286 split-view window and shrink/reset the viewport; no error overlay or console287 error is acceptable. Do not fix a catalog defect with story-local geometry,288 padding, or icon code.2895. Keep found-issue continuity in the prototype's `REVIEW-LEDGER.md`, not in290 long-lived reviewer conversations — one line per finding291 (date · finder · [Pn] finding — file:line → resolution):292293 ```text294 2026-08-11 · mac-design-audit · [P1] centered two-line toolbar title — app/files/page.tsx:24 → left-aligned single line295 2026-08-11 · owner · [P2] glass on content background — app/files/files-surface.tsx:58 → blur moved to toolbar capsule296 2026-08-12 · self · [P3] Unicode ⌕ in search bubble — app/files/files-surface.tsx:71 → SystemSymbol magnifyingglass297 ```298299## When a rule fights you300301These rules encode one team's decisions, not physics. If an invariant blocks302the obvious path, or host specifics (ports, launchd, Tailscale) don't match303this machine, say so and ask the owner rather than silently working around304it — and record the resolution in the project's own docs.