proto-plugin 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/AGENTS.md +308 -29
  2. package/README.md +55 -13
  3. package/package.json +1 -1
  4. package/scripts/lib/host-config.example.sh +1 -1
  5. package/scripts/publish-package.sh +41 -0
  6. package/scripts/verify-prototype-preview-states.mjs +57 -0
  7. package/src/components/platform-ui/controls-panel-select.tsx +4 -3
  8. package/src/components/platform-ui/prototype-tool-dialog.tsx +117 -0
  9. package/src/components/platform-ui/sidebar.tsx +34 -16
  10. package/src/components/prototype-gallery-client.tsx +77 -37
  11. package/src/components/prototypes/design-exploration-variant-preview-shell.tsx +43 -0
  12. package/src/components/prototypes/prototype-brief-field.tsx +33 -0
  13. package/src/components/prototypes/prototype-change-log-panel.tsx +4 -0
  14. package/src/components/prototypes/prototype-comment-storage-empty-state.tsx +41 -0
  15. package/src/components/prototypes/prototype-create-exploration-modal.tsx +41 -64
  16. package/src/components/prototypes/prototype-create-prototype-modal.tsx +102 -0
  17. package/src/components/prototypes/prototype-create-state-modal.tsx +31 -70
  18. package/src/components/prototypes/prototype-default-state-map-page.tsx +4 -4
  19. package/src/components/prototypes/prototype-design-brief-modal.tsx +10 -10
  20. package/src/components/prototypes/prototype-design-brief-panel.tsx +261 -231
  21. package/src/components/prototypes/prototype-floating-pill.module.scss +16 -9
  22. package/src/components/prototypes/prototype-mobbin-gallery.tsx +18 -2
  23. package/src/components/prototypes/prototype-review-sidebar.tsx +11 -28
  24. package/src/components/prototypes/prototype-spec-panel-content.tsx +5 -5
  25. package/src/components/prototypes/prototype-spec-panel-empty-state.tsx +53 -0
  26. package/src/components/prototypes/prototype-state-canvas-overlay.module.scss +20 -47
  27. package/src/components/prototypes/prototype-state-canvas-overlay.tsx +4 -42
  28. package/src/components/prototypes/prototype-variant-explorer.tsx +32 -78
  29. package/src/components/prototypes/prototype-vercel-preview-modal.tsx +4 -3
  30. package/src/components/prototypes/state-map-annotation-tooltip.module.scss +7 -7
  31. package/src/components/shell/prototype-component-library-empty-state.tsx +59 -0
  32. package/src/components/shell/prototype-gallery-nav.tsx +124 -0
  33. package/src/components/shell/prototype-gallery-shell.tsx +45 -13
  34. package/src/components/shell/prototype-link-source-modal.tsx +100 -0
  35. package/src/components/ui/button.tsx +3 -1
  36. package/src/components/ui/dialog.tsx +43 -4
  37. package/src/config/create-prototype-component-library-page.tsx +74 -0
  38. package/src/config/create-prototype-gallery-page.tsx +26 -23
  39. package/src/config/create-prototype-host-app.tsx +3 -15
  40. package/src/config/create-prototype-site-layout.tsx +14 -1
  41. package/src/config/create-prototype-state-map-page.tsx +44 -0
  42. package/src/config/prototype-site-layout-client.tsx +20 -6
  43. package/src/config/with-prototype.ts +21 -0
  44. package/src/index.ts +54 -10
  45. package/src/lib/copy-text-with-attachments.ts +35 -0
  46. package/src/lib/format-source-directory-name.ts +12 -0
  47. package/src/lib/platform-ui/sidebar-interactive-cursor.ts +19 -0
  48. package/src/lib/pr-split/build-pr-split-plan-prompt.ts +81 -0
  49. package/src/lib/pr-split/pr-split-highlight.ts +3 -0
  50. package/src/lib/prototype-comments/core/annotation-target.ts +12 -3
  51. package/src/lib/prototype-comments/core/comment-capture-blocked.ts +24 -0
  52. package/src/lib/prototype-comments/core/screenshot.ts +68 -31
  53. package/src/lib/prototype-comments/core/storage.ts +3 -6
  54. package/src/lib/prototype-comments/core/types.ts +2 -0
  55. package/src/lib/prototype-comments/hooks/useLayoutShift.ts +9 -2
  56. package/src/lib/prototype-comments/react/CommentCaptureToolbar.tsx +45 -10
  57. package/src/lib/prototype-comments/react/CommentProvider.tsx +19 -0
  58. package/src/lib/prototype-comments/ui/CommentModeToggleButton.tsx +34 -0
  59. package/src/lib/prototype-comments/ui/CommentsGrid.tsx +8 -16
  60. package/src/lib/prototype-comments/ui/CommentsSidebar.tsx +5 -19
  61. package/src/lib/prototype-comments/ui/capture-styles.module.scss +5 -1
  62. package/src/lib/prototype-comments/ui/comments-grid-card.module.scss +0 -7
  63. package/src/lib/prototypes/build-design-exploration-renderers.ts +44 -0
  64. package/src/lib/prototypes/comment-storage-setup-prompt.ts +124 -0
  65. package/src/lib/prototypes/create-prototype-prompt.ts +96 -0
  66. package/src/lib/prototypes/create-prototype-registry.ts +5 -0
  67. package/src/lib/prototypes/design-exploration-types.ts +261 -10
  68. package/src/lib/prototypes/link-source-prompt.ts +93 -0
  69. package/src/lib/prototypes/populate-component-library-prompt.ts +64 -0
  70. package/src/lib/prototypes/prototype-config-types.ts +2 -0
  71. package/src/lib/prototypes/prototype-layout-classes.ts +19 -0
  72. package/src/lib/prototypes/prototype-preview-state-registry.ts +5 -2
  73. package/src/lib/prototypes/prototype-review-context.tsx +4 -4
  74. package/src/lib/prototypes/prototype-state-canvas-constants.ts +1 -1
  75. package/src/lib/prototypes/prototype-state-canvas-types.ts +0 -2
  76. package/src/lib/prototypes/prototype-storage-status.ts +31 -0
  77. package/src/lib/prototypes/sort-prototypes-by-creation-date.ts +43 -0
  78. package/src/lib/prototypes/use-prototype-tool-theme.tsx +1 -1
  79. package/src/lib/prototypes/use-toolbar-hover-panel.ts +12 -1
  80. package/src/lib/prototypes/variant-set-lucide-icon.tsx +24 -0
  81. package/src/lib/tool-portal.ts +24 -0
  82. package/src/lib/use-copy-to-clipboard.ts +7 -13
  83. package/src/server/create-host-api-route.ts +7 -16
  84. package/src/server/create-prototype-api-routes.ts +0 -115
  85. package/src/server/prototype-storage-status-route.ts +29 -0
  86. package/src/server.ts +0 -8
  87. package/src/styles/globals.css +359 -71
  88. package/src/lib/prototypes/design-exploration-default-storage.ts +0 -68
  89. package/src/lib/prototypes/design-exploration-default.ts +0 -51
  90. package/src/lib/prototypes/use-design-exploration-default.ts +0 -114
  91. package/src/server/redis/prototype-design-exploration-default.ts +0 -32
package/AGENTS.md CHANGED
@@ -26,6 +26,47 @@ Host prototypes import framework from `prototype` and UI from `@/components/ui`.
26
26
 
27
27
  ---
28
28
 
29
+ ## Viewport layout vs responsive UI
30
+
31
+ The review toolbar **Layout** toggle (desktop / mobile) only changes the **preview frame** — it sets `#prototype-viewport` dimensions and `data-prototype-viewport-layout` for shell chrome (device frame, footer behavior). It must **not** drive product UI differences inside prototype content.
32
+
33
+ **Host prototype content** must respond to **viewport width** only:
34
+
35
+ - Tailwind breakpoints (`sm:`, `md:`, …) or container queries for CSS
36
+ - `matchMedia` / resize observers when JS must branch
37
+
38
+ **Do not use** for layout or component swaps inside `src/prototypes/`:
39
+
40
+ - `prototype-desktop:` / `prototype-mobile:` variants (deprecated; do not reintroduce)
41
+ - `usePrototypeMobileViewportFrame()` / `usePrototypeViewportFrame()` for UI branching
42
+ - `review.viewportLayout` or `data-prototype-viewport-layout` in prototype components
43
+
44
+ The mobile preview frame is 390×844px; the desktop frame fills the preview stage. Breakpoints apply to the **content viewport width** — the same surface users see in production.
45
+
46
+ ---
47
+
48
+ ## Tool theme vs prototype preview content
49
+
50
+ The gallery/review **light/dark mode toggle** only changes **tool chrome** (sidebar, header, gallery nav, modals). It must **never** change colors, typography, or tokens inside prototype preview content (`#prototype-viewport` / `[data-prototype-screenshot]`).
51
+
52
+ **Tool theme** is scoped to `[data-prototype-root]` via `--tool-chrome-*` and `data-prototype-comment-theme`. Tool UI uses those tokens (or Tailwind utilities whose `--color-*` values resolve from `[data-prototype-root]`).
53
+
54
+ **Prototype preview content** uses fixed **product** tokens from the synced source app. Host `globals.css` defines `--product-*` names and maps them to shadcn tokens inside `[data-prototype-screenshot]` only. Package CSS resets all semantic and `--color-*` tokens on `[data-prototype-screenshot]` so tool theme cannot cascade in.
55
+
56
+ **Do not use** for styling inside `src/prototypes/`:
57
+
58
+ - `usePrototypeToolTheme()` / `useLightTheme` / `commentTheme`
59
+ - `data-prototype-comment-theme` on prototype components
60
+ - `--tool-chrome-*` tokens in prototype JSX or CSS
61
+
62
+ **Do not** in package or host CSS:
63
+
64
+ - Bridge shadcn tokens (`--background`, `--foreground`, `--card`, …) to `--tool-chrome-*` on `[data-prototype-root]`
65
+ - Map host `@theme inline` `--color-*` to `var(--background)` — use self-referential `var(--color-*)` for tool chrome; product tokens belong in `[data-prototype-screenshot]` only
66
+ - Put product token values on `:root` without the `--product-*` prefix (tool theme would override them)
67
+
68
+ ---
69
+
29
70
  ## Relationship to source app (host apps)
30
71
 
31
72
  Host apps replicate product UI from the **source application** (linked read-only via `SOURCE_PATH` and `pnpm link-source`) **exactly** — standalone code, no runtime imports from the source monorepo.
@@ -66,7 +107,7 @@ The sync/link scripts are host-agnostic — package code names nothing host-spec
66
107
 
67
108
  Default to **mock data only**.
68
109
 
69
- Reference prototype: `example-feature` — `src/prototypes/example-feature/` (or any registered slug in your host app).
110
+ Reference prototype: `booking-flow` — `src/prototypes/booking-flow/` (or any registered slug in your host app).
70
111
 
71
112
  ---
72
113
 
@@ -79,8 +120,8 @@ Shareable UI at `/prototypes/[slug]`. Registered in `prototype.config.ts`.
79
120
  ### Rules (mandatory)
80
121
 
81
122
  1. **Register** the slug in `prototype.config.ts` and add a screenshot under `public/prototypes/screenshots/`.
82
- 2. **Create** `src/prototypes/<slug>/component-ids.ts` — every component target id (see `example-feature/component-ids.ts`).
83
- 3. **Wire comments** with `usePrototypeComments` on the page (see `example-feature/index.tsx`).
123
+ 2. **Create** `src/prototypes/<slug>/component-ids.ts` — every component target id (see `booking-flow/component-ids.ts`).
124
+ 3. **Wire comments** with `usePrototypeComments(liveState, onRestore)` on the page (see `booking-flow/index.tsx`).
84
125
  4. **Wrap the root JSX of every React component** in `PrototypeComponent` from `prototype`.
85
126
  5. **On-page prototype controls** (state pickers, variant tabs, preview toggles) must use `PrototypeControl` or `PrototypeControlGroup`, not plain `PrototypeComponent`.
86
127
  6. **Use ids from the registry only** — dev mode logs an error for unregistered ids.
@@ -88,9 +129,58 @@ Shareable UI at `/prototypes/[slug]`. Registered in `prototype.config.ts`.
88
129
  - File's main export: kebab-case filename (`settings-section.tsx` → `settings-section`; `index.tsx` → `page`).
89
130
  - Other functions in the same file: `{filename}.{function-kebab}`.
90
131
  - Dynamic list items: add a prefix to `dynamicPrefixes` in `component-ids.ts`.
91
- 8. **Scroll container:** `PrototypeComponent` with `id="scroll-container"` on the main `overflow-y-auto` wrapper.
132
+ 8. **Scroll container:** `PrototypeComponent` with `id="scroll-container"` see **Prototype viewport fill** below.
92
133
  9. **Do not** use `data-testid` for comment anchoring.
93
134
 
135
+ ### Prototype viewport fill (avoid offset / clipping)
136
+
137
+ There are **two** common offset bugs — do not confuse them:
138
+
139
+ | Symptom | Cause | Fix |
140
+ |--------|--------|-----|
141
+ | Prototype preview pinned to top-left; empty space + clipping on right/bottom | Missing `h-full` fill chain inside `#prototype-viewport` | Use layout constants below on every nested wrapper |
142
+ | Gallery / sidebar with black gaps + white bar at bottom of window | Site shell height mismatch (`h-full` parent + `h-svh` child, or body bg showing through) | Do **not** change gallery shell height classes ad hoc — parent route wrapper owns `h-svh`; `[data-prototype-root]` fills with `h-full flex-1` |
143
+
144
+ The preview shell (`#prototype-viewport`) only auto-sizes **direct** children of `.viewportPage` (`width/height: 100%`, column flex). Every nested layer must repeat the fill chain or content shrinks to the top-left and clips on the opposite edge.
145
+
146
+ **Use the exported constants** (do not hand-roll variants):
147
+
148
+ ```tsx
149
+ import {
150
+ PROTOTYPE_PAGE_CLASS,
151
+ PROTOTYPE_SCROLL_CONTAINER_CLASS,
152
+ PROTOTYPE_VIEW_SHELL_CLASS,
153
+ } from "prototype";
154
+ ```
155
+
156
+ **Required page shell** (match `home-page` / `proto-partner-page`):
157
+
158
+ ```tsx
159
+ <PrototypeComponent
160
+ id="scroll-container"
161
+ className={cn(PROTOTYPE_SCROLL_CONTAINER_CLASS, "…")}
162
+ >
163
+ <PrototypeComponent id="page" className={cn(PROTOTYPE_PAGE_CLASS, "…")}>
164
+ ```
165
+
166
+ **Every step/view root** (including `AnimatePresence` / motion wrappers):
167
+
168
+ ```tsx
169
+ className={cn(PROTOTYPE_VIEW_SHELL_CLASS, "…")}
170
+ ```
171
+
172
+ Put `overflow-y-auto` on the **view** that scrolls, not on `scroll-container`, unless the entire page is one scroll surface.
173
+
174
+ **Centered embed/card layouts** (public booking pages): outer view = `w-full flex-1`; inner content = `mx-auto w-full max-w-*`. Use top padding (`py-10`) for embed pages — avoid `justify-center` on a `flex-1` wrapper when content can exceed viewport height (mobile stack); it vertically centers overflow and hides content off-screen.
175
+
176
+ **Motion:** Prefer opacity-only cross-fades on page-level wrappers. Avoid horizontal `translateX` on wrappers that lack explicit `w-full` — `#prototype-viewport` uses `transform: translateZ(0)`, and transform + shrink-wrapped children produces the common top-left / clipped-right offset bug.
177
+
178
+ **Post-build layout check (mandatory before finishing):**
179
+
180
+ 1. Open `/` — sidebar flush to content, no black gutter, no white strip at bottom of window.
181
+ 2. Open the prototype at desktop width — main surface fills the preview frame, not hugging a corner.
182
+ 3. Toggle mobile preview — tall stacked pages still scroll; nothing hidden off-screen from `justify-center`.
183
+
94
184
  ### Examples
95
185
 
96
186
  ```tsx
@@ -106,14 +196,27 @@ export function SettingsSection(props: SettingsSectionProps) {
106
196
  ```
107
197
 
108
198
  ```tsx
109
- import { PrototypeControl } from "prototype";
110
-
111
- export function PrototypePreviewStateSelect(...) {
112
- return (
113
- <PrototypeControl id="prototype-preview-state-select" className="flex items-center gap-2">
114
- ...
115
- </PrototypeControl>
116
- );
199
+ import { ControlsPanelSelect } from "@prototype/components/platform-ui/controls-panel-select";
200
+ import { usePrototypeReviewOptional } from "@prototype/lib/prototypes/prototype-review-context";
201
+ import { useLayoutEffect } from "react";
202
+
203
+ export function PrototypePreviewStateSelect({ stateId, onStateChange, options }) {
204
+ const setTweaksContent = usePrototypeReviewOptional()?.setTweaksContent;
205
+
206
+ useLayoutEffect(() => {
207
+ if (!setTweaksContent) return;
208
+ setTweaksContent(
209
+ <ControlsPanelSelect
210
+ appearance="menuList"
211
+ value={stateId}
212
+ onValueChange={onStateChange}
213
+ options={options}
214
+ />,
215
+ );
216
+ return () => setTweaksContent(null);
217
+ }, [onStateChange, options, setTweaksContent, stateId]);
218
+
219
+ return null;
117
220
  }
118
221
  ```
119
222
 
@@ -124,15 +227,115 @@ export function PrototypePreviewStateSelect(...) {
124
227
  3. Add `component-ids.ts` — exhaustive static id list + `dynamicPrefixes`.
125
228
  4. Register in `prototype.config.ts` with `componentRegistry`.
126
229
  5. Wrap every UI component in `PrototypeComponent` / `PrototypeControl`.
127
- 6. Call `usePrototypeComments(liveState, onRestore)` on the page.
128
- 7. Run `pnpm verify:prototype-ids`.
129
- 8. Capture screenshot; share via `pnpm share-prototype <slug>`.
230
+ 6. Define **`liveState`** and wire `usePrototypeComments(liveState, onRestore)` see **Live state & preview states**.
231
+ 7. **Enumerate preview modes** from the brief and source (tabs, empty/loading/error, filters, modals, view toggles, wizard steps) and add the full preview-state stack in the **same change** as the UI:
232
+ - `*-preview-states.ts` (`definePreviewStateRegistry`, `createLiveStateForPreview`)
233
+ - `*-preview-state-select.tsx` (`ControlsPanelSelect` with `appearance="menuList"` via `setTweaksContent` — not a floating pill on the page)
234
+ - `*-state-canvas-config.tsx`
235
+ - `state-map-page.tsx` + `stateMapComponent` in `prototype.config.ts`
236
+ - `review.setStateCanvasConfig(...)` in `index.tsx`
237
+ 8. Run `pnpm verify:prototype-ids` and `pnpm verify:prototype-preview-states`.
238
+ 9. Capture screenshot; share via `pnpm share-prototype <slug>`.
239
+
240
+ ---
241
+
242
+ ## Live state & preview states (mandatory)
243
+
244
+ Prototype state is a first-class tool feature — comments, share links, the review **State** map, and PR split cards all read from the same snapshot. **Do not invent parallel state systems.** Use the plugin's live-state + preview-state registry pattern on every prototype that has reviewable UI modes.
245
+
246
+ ### Do this proactively
247
+
248
+ When **building or modifying** a prototype, treat preview states as part of the feature — not a follow-up:
249
+
250
+ 1. **Read existing state first** — `index.tsx`, any `*-preview-states.ts`, design-exploration configs, and `pr-split-config.ts` before changing UI.
251
+ 2. **Define a typed `TLiveState`** — one JSON-serializable object holding every field a reviewer needs to reproduce the view (step/screen, modal open, form values, toggles, selected tab, etc.).
252
+ 3. **One `liveState` on the page** — `useState<TLiveState>(createDefaultLiveState)` (or hydrate from `readPersistedPrototypeLiveState`). Derive rendered UI from `liveState`; route interactions through setters that update it.
253
+ 4. **Wire comments** — `usePrototypeComments(liveState, onRestore)` where `onRestore` calls `setLiveState(restored)`. This powers annotation restore and `shareState` links. Do **not** roll custom `?step=` / `?view=` query params.
254
+ 5. **Preview states are part of the feature, not a follow-up** — for feature pages, add or update a single `definePreviewStateRegistry` in `*-preview-states.ts` (see below). Every distinct tab, empty/loading/error branch, filter mode, modal, view toggle, or wizard step reviewers should jump to gets a registry entry **in the same change** as the UI. Only skip the registry for truly static single-view pages with no reviewable modes.
255
+ 6. **Wire the state picker into the review toolbar** — `*-preview-state-select.tsx` calls `review.setTweaksContent(...)` with `ControlsPanelSelect` using `appearance="menuList"` and `pickerOptions` from the registry. Do **not** render a floating `miniPill` select on the prototype surface (see `booking-flow-preview-state-select.tsx`).
256
+ 7. **Wire the state map** — `*-state-canvas-config.tsx` builds `PrototypeStateCanvasConfig` from the same registry. In `index.tsx`, call `review.setStateCanvasConfig(...)` in an effect; depend on stable review callbacks, not the whole `review` object. Add a `stateMapComponent` on the prototype in `prototype.config.ts` (see `booking-flow/state-map-page.tsx`) so `/prototypes/<slug>/states` renders the map instead of the default empty page.
257
+ 8. **Verify** — run `pnpm verify:prototype-preview-states` whenever a `*-preview-states.ts` file exists.
258
+
259
+ Optional: `usePersistPrototypeLiveState(slug, liveState)` keeps reviewer position across reloads.
260
+
261
+ ### Architecture
262
+
263
+
264
+ | Piece | Location | Purpose |
265
+ | ----- | -------- | ------- |
266
+ | `TLiveState` + `liveState` | page `index.tsx` | Single snapshot of all preview-relevant UI |
267
+ | `usePrototypeComments` | page | Freeze/restore `liveState` for comments & share links |
268
+ | `*-preview-states.ts` | `_components/` | `definePreviewStateRegistry` — picker, map nodes, `edges`, sections |
269
+ | `*-preview-state-select.tsx` | `_components/` | On-page / sidebar step picker (`PrototypeControl`) |
270
+ | `*-state-canvas-config.tsx` | `_components/` | Wireframes + registry data for the review **State** panel |
271
+
272
+
273
+ Reference: `src/prototypes/booking-flow/` (`index.tsx`, `booking-flow-preview-states.ts`, `booking-flow-preview-state-select.tsx`, `booking-flow-state-canvas-config.tsx`).
274
+
275
+ ### Page wiring (minimal)
276
+
277
+ ```tsx
278
+ const [liveState, setLiveState] = useState<TLiveState>(createDefaultLiveState);
279
+
280
+ const onRestore = useCallback((restored: TLiveState) => {
281
+ setLiveState(restored);
282
+ }, []);
283
+
284
+ usePrototypeComments(liveState, onRestore);
285
+
286
+ const setStateCanvasConfig = review?.setStateCanvasConfig;
287
+ useEffect(() => {
288
+ if (!setStateCanvasConfig) return;
289
+ setStateCanvasConfig(
290
+ buildMyStateCanvasConfig(setStep),
291
+ );
292
+ return () => setStateCanvasConfig(null);
293
+ }, [setStateCanvasConfig, setStep]);
294
+ ```
295
+
296
+ ### When to add a preview-state registry entry
297
+
298
+ Add (or update) a registry state when you introduce:
299
+
300
+ - A new screen, step, wizard stage, or primary tab reviewers should open directly
301
+ - A modal, drawer, or overlay branch worth reviewing on its own
302
+ - Empty, loading, or error branches that are distinct preview modes
303
+
304
+ Skip registry entries for hover, focus, or other non-shareable micro-interactions.
305
+
306
+ ### Simple prototypes without steps
307
+
308
+ If there are no navigable preview modes, `liveState` may be `{}` or hold only a few toggles — still call `usePrototypeComments`. Design explorations register separately via variant sets; do not fold exploration variants into ad-hoc `useState` without also exposing them through the exploration config.
309
+
310
+ Multi-route prototypes (e.g. `proto-partner-page`) may use Next routes for real page boundaries; in-prototype step/modal switching within a page still belongs in `liveState`.
311
+
312
+ ### Anti-patterns
313
+
314
+ - Multiple independent `useState` hooks for step/modal/view **without** aggregating into `liveState`
315
+ - Custom URL query params for preview mode — share links use `usePrototypeComments` + encoded `shareState`
316
+ - Hand-rolled `<select>` / tabs for preview modes outside `ControlsPanelSelect` + the registry
317
+ - Floating `ControlsPanelSelect` with `appearance="miniPill"` on the prototype surface — states belong in the review toolbar **States** menu via `appearance="menuList"` and `setTweaksContent`
318
+ - Duplicating picker labels or state ids outside `definePreviewStateRegistry`
319
+ - Adding UI steps or modal branches **without** updating the registry, picker, and state map in the same change
320
+ - Separate state maps or pickers that are not imported from the single `*-preview-states.ts` file
321
+ - Passing `liveState` or the current preview mode into the state map config — the map is a **stateless** navigation surface, not a mirror of the active state
322
+ - Tracking `activeStateId` / selected styling / checkmarks on state map nodes
130
323
 
131
324
  ---
132
325
 
133
326
  ## Preview states & state map
134
327
 
135
- Prototypes with preview modes use `definePreviewStateRegistry` in `*-preview-states.ts`. One registry drives the state picker, map nodes, `edges`, and sections.
328
+ Prototypes with preview modes use `definePreviewStateRegistry` in `*-preview-states.ts`. One registry drives the state picker, map nodes, `edges`, and sections. The picker, canvas config, and verification script must all import from this file — never maintain parallel state lists.
329
+
330
+ ### State map behavior (mandatory)
331
+
332
+ The state map is a **stateless picker** — it must **not** reflect, highlight, or mark the current preview state.
333
+
334
+ - **No active-state association** — Do not pass the current preview mode, `liveState`, or any `activeStateId` into `PrototypeStateCanvasConfig` or `build…StateCanvasConfig`. Clicking a node navigates to that state; there is no checkmark, selected border, or `aria-current` on the map.
335
+ - **Follows tool theme** — The state map canvas (background, chrome, edges, section labels, node cards) uses `--tool-chrome-*` tokens and matches the gallery/review light/dark mode toggle.
336
+ - **`build…StateCanvasConfig(onStateSelect)`** — The builder takes only the select handler. Wire it once in `index.tsx` and `state-map-page.tsx`; do not re-build when `liveState` changes.
337
+
338
+ Reference: `src/prototypes/booking-flow/_components/booking-flow-state-canvas-config.tsx`, `state-map-page.tsx`.
136
339
 
137
340
  ### State map layout
138
341
 
@@ -142,7 +345,7 @@ When two states are connected by an `edges` entry, arrange `canvasLayout.rows` s
142
345
 
143
346
  Prefer adjacent columns on the same row, or use `{ id, column }` / `startColumn` to offset nodes so vertical edges do not cut through unrelated cards in the same column.
144
347
 
145
- Reference: `src/prototypes/example-feature/_components/list-preview-states.ts`.
348
+ Reference: `src/prototypes/booking-flow/_components/booking-flow-preview-states.ts`.
146
349
 
147
350
  ---
148
351
 
@@ -153,11 +356,33 @@ Use when comparing **2+ UI directions** for the same user problem.
153
356
 
154
357
  | Layer | Location | Owns |
155
358
  | ------------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------------- |
156
- | **Tool** | `prototype` (`PrototypeVariantExplorer`, etc.) | Modal chrome, variant tabs, rationale popover, Mobbin gallery, localStorage for brief fields |
157
- | **Prototype** | `src/prototypes/<slug>/_components/` | Variant renderers, copy, rationale, context JSON, Mobbin references, `component-ids.ts` |
359
+ | **Tool** | `prototype` (`PrototypeVariantExplorer`, etc.) | Modal chrome, variant tabs, Mobbin gallery, localStorage for brief fields |
360
+ | **Prototype** | `src/prototypes/<slug>/_components/` | Variant renderers, context JSON, Mobbin references, `component-ids.ts` |
361
+
158
362
 
363
+ Reference: `src/prototypes/proto-partner-page/_components/invite-copy-design-exploration-config.tsx` + `invite-copy-variant-toggle.tsx`.
159
364
 
160
- Reference: `example-feature/_components/no-data-design-exploration-config.tsx` + `no-data-variant-toggle.tsx`.
365
+ ### Renderers
366
+
367
+ Build one preview per option with `buildDesignExplorationRenderers`. In host prototype files (client components), import from `@prototype/...` — not the `proto-plugin` barrel, which includes server-only modules:
368
+
369
+ ```ts
370
+ import { buildDesignExplorationRenderers } from "@prototype/lib/prototypes/build-design-exploration-renderers";
371
+ import { DesignExplorationVariantPreviewShell } from "@prototype/components/prototypes/design-exploration-variant-preview-shell";
372
+
373
+ const renderers = buildDesignExplorationRenderers(VARIANT_OPTIONS, (variant) => (
374
+ <MyBlock variant={variant} />
375
+ ), BASELINE);
376
+
377
+ // Overlay-style variants (absolute positioning on the live page):
378
+ const renderers = buildDesignExplorationRenderers(VARIANT_OPTIONS, (variant) => (
379
+ <DesignExplorationVariantPreviewShell layout={isOverlay(variant) ? "overlay" : "inline"}>
380
+ <MyBlock variant={variant} />
381
+ </DesignExplorationVariantPreviewShell>
382
+ ), BASELINE);
383
+ ```
384
+
385
+ Do not hand-roll renderer maps that return `null` for some variants — the helper maps every option and warns in dev when a renderer returns empty content.
161
386
 
162
387
  ### Config shape
163
388
 
@@ -167,22 +392,42 @@ Reference: `example-feature/_components/no-data-design-exploration-config.tsx` +
167
392
  variantTabsIdPrefix: string;
168
393
  storageKeyPrefix: string;
169
394
  variant, onVariantChange,
170
- options: { value, label, hint? }[];
171
- rationale: Record<TVariant, { good, bad }>;
395
+ options: { value, label, hint? }[]; // exploration directions — newest first; exclude baseline when possible
172
396
  renderers: Record<TVariant, ComponentType>;
173
397
  brief: { titleDefault, descriptionDefault };
398
+ baseline: { value, label?, hint? }; // pre-exploration UI — always pinned at the bottom of the variant list
399
+ defaultVariant?: TVariant; // codebase-stored default (e.g. DEFAULT_*_VARIANT); marked "Default" in the brief
174
400
  context?: { label, data: unknown, render?, defaultExpanded?, panelId? };
175
401
  mobbin?: { references, title?, description?, imagePathForReference? };
176
402
  variantsSection?: { title?, description? };
177
403
  }
178
404
  ```
179
405
 
406
+ Every exploration **must** define `baseline` — the UI as it existed before the exploration was added. The design brief shows it last with an **Original** badge. Use `getDesignExplorationDisplayOptions(options, baseline)` for tabs and keyboard cycling; use `getDesignExplorationVariantOptions(options, baseline)` when listing directions in prompts.
407
+
408
+ Build renderers with `buildDesignExplorationRenderers(options, renderVariant, baseline)` so the baseline preview is included.
409
+
410
+ ### Exploration icons
411
+
412
+ The review sidebar explorations menu shows a Lucide icon per variant set via `VariantSetLucideIcon` (`packages/prototype/src/lib/prototypes/variant-set-lucide-icon.tsx`). Rules match on `id` + `label` (first fit wins); unmatched sets fall back to `LayoutGrid`.
413
+
414
+ When adding a new exploration, **assign it a distinct icon**:
415
+
416
+ 1. Pick a Lucide icon that reflects the exploration topic (not `LayoutGrid` unless nothing else fits).
417
+ 2. Add a `{ test: /…/, icon: … }` entry to `VARIANT_SET_ICON_RULES`, placed **above** broad rules such as `/mobile/i`, so this exploration wins over generic matchers.
418
+ 3. Do not reuse icons already assigned to other explorations on the same prototype.
419
+ 4. Import the chosen icon from `lucide-react`.
420
+
421
+ The default variant lives in the codebase config (`defaultVariant`, typically `DEFAULT_*_VARIANT`) — not KV. In the brief, each non-default option shows a **Make default** button that copies a prompt (`buildSetDefaultVariantPrompt`) asking an agent to update that constant; the current default is marked **Default**.
422
+
180
423
  ### Iterating on variants
181
424
 
182
425
  When asked to **modify an existing option**, add a **new variant** (do not replace in place), register ids, and update `DEFAULT_*_VARIANT`.
183
426
 
184
427
  **Variant list order:** prepend each new entry to the top of `*_VARIANT_OPTIONS` (first item in the array). That order drives the design-brief sidebar/modal scroll view and variant tabs — newest explorations appear at the top.
185
428
 
429
+ In review mode, each exploration sidebar includes a brief field and **Copy prompt** button below the header. It copies `buildMoreDesignExplorationVariantsCopyText` with the typed brief, existing variants, and config path — use that to request more layout directions from an agent.
430
+
186
431
 
187
432
  | Prompt intent | Action |
188
433
  | --------------------- | ----------------------------------------------- |
@@ -203,7 +448,7 @@ Use when a prototype ships as a **sequence of small source PRs** rather than one
203
448
  | **Prototype** | `src/prototypes/<slug>/_components/` | `PR_SPLIT_ENTRIES` data, `PR_SPLIT_CONFIG`, wireframe renderers, live-state URL wiring |
204
449
 
205
450
 
206
- Reference: `example-feature/_components/pr-split-config.ts` + `pr-split-wireframes.tsx`.
451
+ Reference: `booking-flow/_components/pr-split-config.ts` + `pr-split-wireframes.tsx` (when present) or any prototype with a spec panel.
207
452
 
208
453
  ### Host setup
209
454
 
@@ -279,15 +524,16 @@ export const PR_SPLIT_ENTRIES: MyPrSplitEntry[] = [
279
524
 
280
525
  ### Wireframes (`pr-split-wireframes.tsx`)
281
526
 
282
- Prototype-specific schematic thumbnails for each `wireframeId`. Pattern:
527
+ Prototype-specific **full-page** schematic previews for each `wireframeId`. Each card must show the complete page shell (sidebar, header/toolbar, main content) — not a cropped excerpt or zoomed-in slice of the PR target. Pattern:
283
528
 
284
- 1. Define a `PageShell` (or layout scaffold) matching the prototype page structure.
285
- 2. Use low-fidelity `Wire` blocks — neutral grays for chrome, **highlighted** regions for the PR target.
529
+ 1. Define a `PageShell` (or layout scaffold) matching the prototype's real page structure — every zone reviewers see in the live preview.
530
+ 2. Use low-fidelity `Wire` blocks — neutral grays for chrome, **highlighted** regions for the PR target only.
286
531
  3. Import highlight tokens from the tool: `PR_TARGET_HIGHLIGHT_BORDER`, `PR_TARGET_HIGHLIGHT_FILL`.
287
- 4. Register one renderer per `wireframeId` in a `Record<WireframeId, () => ReactNode>`.
288
- 5. Export `PrSplitWireframe` wrapped in `PrototypeComponent` (register id in `component-ids.ts`).
532
+ 4. Fill the card preview: wrap the wireframe root in `PR_SPLIT_WIREFRAME_FRAME_CLASS` from `prototype` (`aspect-video w-full overflow-hidden`) so the schematic reads as a full screen, not a short strip.
533
+ 5. Register one renderer per `wireframeId` in a `Record<WireframeId, () => ReactNode>`.
534
+ 6. Export `PrSplitWireframe` wrapped in `PrototypeComponent` (register id in `component-ids.ts`).
289
535
 
290
- Highlighted areas must match what `targetId` points to on the real prototype.
536
+ Highlighted areas must match what `targetId` points to on the real prototype; surrounding chrome provides context for where the change sits on the page.
291
537
 
292
538
  ### Page wiring (`index.tsx`)
293
539
 
@@ -326,6 +572,34 @@ setSpecContent(
326
572
 
327
573
  ---
328
574
 
575
+ ## Consuming the review context in effects
576
+
577
+ `usePrototypeReview()` / `usePrototypeReviewOptional()` return a **new context object on most renders** — the provider's `value` memo rebuilds whenever review state changes (e.g. `variantSets`, panel open state). Several of its methods (`registerVariantSet`, `unregisterVariantSet`, etc.) also call `setState`.
578
+
579
+ **Never put the whole `review` object in an effect dependency array.** Doing so creates an infinite loop ("Maximum update depth exceeded"): the effect calls a method → state changes → `value` gets a new identity → the effect re-runs → its cleanup undoes the work → the body redoes it → repeat.
580
+
581
+ Depend on the **specific stable callbacks** instead. The context methods are wrapped in `useCallback(..., [])`, so their identities are stable across renders.
582
+
583
+ ```tsx
584
+ // ❌ loops forever — `review` identity changes whenever it registers/unregisters
585
+ useLayoutEffect(() => {
586
+ if (!review) return;
587
+ review.registerVariantSet(set);
588
+ return () => review.unregisterVariantSet(set.id);
589
+ }, [review]);
590
+
591
+ // ✅ stable deps — runs once, cleans up once
592
+ const registerVariantSet = review?.registerVariantSet;
593
+ const unregisterVariantSet = review?.unregisterVariantSet;
594
+ useLayoutEffect(() => {
595
+ if (!registerVariantSet || !unregisterVariantSet) return;
596
+ registerVariantSet(set);
597
+ return () => unregisterVariantSet(set.id);
598
+ }, [registerVariantSet, unregisterVariantSet]);
599
+ ```
600
+
601
+ ---
602
+
329
603
  ## Comment system
330
604
 
331
605
  - Storage: Upstash Redis key `prototype-comments:<slug>`
@@ -347,4 +621,9 @@ Before finishing, confirm side-by-side with source or a screenshot:
347
621
  - Importing from `source/` at runtime
348
622
  - Reimplementing synced `@/components/ui` primitives
349
623
  - Approximating layout dimensions or colors
624
+ - **Inventing custom prototype state** instead of `liveState` + `definePreviewStateRegistry` (see **Live state & preview states**)
625
+ - **Adding screens/modals without registering preview states** in the same change
626
+ - Depending on the whole `review` context object in an effect's deps (see "Consuming the review context in effects") — use the specific stable callbacks instead
627
+ - **`scroll-container` with `overflow-y-auto` and no `h-full` fill chain** — causes top-left offset and clipped content in the preview frame (see **Prototype viewport fill**)
628
+ - **Page-level motion with `translateX` on wrappers missing `w-full flex-1`** — same offset bug; use opacity-only or fill classes first
350
629
 
package/README.md CHANGED
@@ -64,14 +64,32 @@ import { withPrototype } from "./packages/prototype/src/config/with-prototype";
64
64
  export default withPrototype({ nextConfig: { /* … */ } });
65
65
  ```
66
66
 
67
- 5. **Tailwind** — scan prototype package components from the host `globals.css`:
67
+ 5. **Tailwind** — scan prototype package components from the host `globals.css`. Register semantic `--color-*` tokens as self-referential vars so utilities like `text-muted-foreground` resolve from `[data-prototype-root]` at runtime — **do not** map `--color-foreground` / `--color-background` to product `:root` tokens (that breaks gallery contrast):
68
68
 
69
69
  ```css
70
70
  @import "tailwindcss";
71
71
  @source "../../packages/prototype/src/**/*.{js,ts,jsx,tsx}";
72
+
73
+ @theme inline {
74
+ --color-foreground: var(--color-foreground);
75
+ --color-muted-foreground: var(--color-muted-foreground);
76
+ /* …other semantic colors used by proto-plugin */
77
+ }
78
+ ```
79
+
80
+ Product tokens use separate names (e.g. `--product-background`) scoped to `[data-prototype-screenshot]`.
81
+
82
+ 6. **Root layout + tool themes** — wrap routes with `createPrototypeSiteLayout()` and import package chrome CSS (dark/light themes live here, not in host `globals.css`):
83
+
84
+ ```tsx
85
+ import { createPrototypeSiteLayout } from "proto-plugin";
86
+ import "proto-plugin/styles/globals.css";
87
+ import "./globals.css";
88
+
89
+ const PrototypeSiteLayout = createPrototypeSiteLayout();
72
90
  ```
73
91
 
74
- 6. **tsconfig paths** (optional, for tool internal `@prototype/*` alias when developing the package):
92
+ 7. **tsconfig paths** (optional, for tool internal `@prototype/*` alias when developing the package):
75
93
 
76
94
  ```json
77
95
  {
@@ -84,6 +102,18 @@ export default withPrototype({ nextConfig: { /* … */ } });
84
102
  }
85
103
  ```
86
104
 
105
+ 8. **Dev server port** — run the host on port **1985** so it stays separate from a typical source app on 3000. In the host `package.json`:
106
+
107
+ ```json
108
+ {
109
+ "scripts": {
110
+ "dev": "next dev -p 1985"
111
+ }
112
+ }
113
+ ```
114
+
115
+ Open [http://localhost:1985](http://localhost:1985) when developing.
116
+
87
117
  ## Public API
88
118
 
89
119
  | Export | Use |
@@ -116,6 +146,8 @@ The `prototype` package uses the **host design system** — same tokens and Tail
116
146
 
117
147
  Host `globals.css` must `@source` prototype package TSX for Tailwind utilities.
118
148
 
149
+ **Do not** implement gallery/review dark or light mode in host `globals.css`. Import `proto-plugin/styles/globals.css` in `layout.tsx` — the package defines both themes on `[data-prototype-root]` (default dark; `data-prototype-comment-theme="light"` for light). Host `globals.css` is for **product** tokens synced from the source app (prototype preview content only).
150
+
119
151
  ## Publish
120
152
 
121
153
  From `packages/prototype/`:
@@ -190,21 +222,21 @@ pnpm download-mobbin-references -- --ids=uuid1,uuid2
190
222
 
191
223
  ## Starter prompt (for AI agents)
192
224
 
193
- Copy the block below into a new agent chat to scaffold a host app from scratch. The agent should ask you for a few values up front, then handle the rest (including inferring what to sync from source).
225
+ Copy the block below into a new agent chat to scaffold a host app from scratch. The agent should ask you for a few values up front — **one question at a time** — then handle the rest (including inferring what to sync from source).
194
226
 
195
227
  ````markdown
196
228
  Set up a new Next.js host app for `proto-plugin` — a framework for shareable UI prototypes.
197
229
 
198
230
  ## Before you start — ask me for these
199
231
 
200
- Do not scaffold or run commands until I answer. Ask in one message:
232
+ Do not scaffold or run commands until I answer all four questions. Ask **one question at a time** — wait for my reply before asking the next:
201
233
 
202
- 1. **Host app name** — directory name for the new Next.js project.
203
- 2. **Where to create it** — parent directory (absolute path), or confirm the current workspace.
204
- 3. **Source app path** — absolute path to the real product repo whose UI we will prototype (used for `source/` symlink and sync).
205
- 4. **Optional env** — whether I want comment/screenshot persistence configured now (`KV_REST_API_URL`, `KV_REST_API_TOKEN`, `BLOB_READ_WRITE_TOKEN`) or to skip for later.
234
+ 1. **Host app name** — What should the new Next.js project directory be called? (e.g. `my-product-prototypes`)
235
+ 2. **Where to create it** — Parent directory as an absolute path (e.g. `/Users/maayanalbert/Projects`), or confirm you want it in the current workspace.
236
+ 3. **Source app path** — Absolute path to the real product repo whose UI we'll prototype (used for the `source/` symlink and sync).
237
+ 4. **Optional env** — Should I configure comment/screenshot persistence now (`KV_REST_API_URL`, `KV_REST_API_TOKEN`, `BLOB_READ_WRITE_TOKEN`), or skip that for later?
206
238
 
207
- Wait for my answers, then do all setup yourself.
239
+ After I answer all four, do all setup yourself.
208
240
 
209
241
  ## 1. Scaffold the app
210
242
 
@@ -229,19 +261,29 @@ Follow the package README (`node_modules/proto-plugin/README.md`) and implement
229
261
  1. **`prototype.config.ts`** at the repo root — start with an empty `prototypes: []` array using `definePrototypeConfig` from `proto-plugin`.
230
262
  2. **Gallery home** — `src/app/page.tsx` via `createPrototypeGalleryPage(prototypeConfig)`.
231
263
  3. **Prototype route** — `src/app/prototypes/[slug]/page.tsx` via `createPrototypePage(prototypeConfig)` (export `default` + `generateStaticParams`).
232
- 4. **Root layout** — wrap children with `createPrototypeSiteLayout()` from `proto-plugin` in `src/app/layout.tsx`.
264
+ 4. **Root layout** — wrap children with `createPrototypeSiteLayout()` from `proto-plugin` in `src/app/layout.tsx`, and import the tool chrome theme:
265
+
266
+ ```tsx
267
+ import { createPrototypeSiteLayout } from "proto-plugin";
268
+ import "proto-plugin/styles/globals.css";
269
+ import "./globals.css";
270
+ ```
233
271
  5. **API catch-all** — `src/app/api/[...path]/route.ts` using `createPrototypeApiRoute(prototypeConfig)` from `proto-plugin/server`; export `GET`, `POST`, and `PUT` handlers that call `dispatch`.
234
272
  6. **`next.config.ts`** — wrap with `withPrototype` from `proto-plugin/with-prototype` and ensure `proto-plugin` is transpiled.
235
- 7. **`src/app/globals.css`** — keep Tailwind v4 setup and add:
273
+ 7. **`src/app/globals.css`** — Tailwind v4 + product tokens synced from source. **Do not** put tool chrome dark/light mode here (no `prefers-color-scheme`, no hardcoded gallery colors). Scan package components:
236
274
 
237
275
  ```css
276
+ @import "tailwindcss";
238
277
  @source "../../node_modules/proto-plugin/src/**/*.{js,ts,jsx,tsx}";
239
278
  ```
240
279
 
241
- 8. **`package.json` scripts** forward CLI commands to the installed package:
280
+ Tool themes live in `proto-plugin/styles/globals.css` (import in layout). The package toggles dark/light on `[data-prototype-root]` via `data-prototype-comment-theme`.
281
+
282
+ 8. **`package.json` scripts** — set dev to port 1985 and forward CLI commands to the installed package:
242
283
 
243
284
  ```json
244
285
  {
286
+ "dev": "next dev -p 1985",
245
287
  "link-source": "pnpm --dir node_modules/proto-plugin link-source",
246
288
  "sync-from-source": "pnpm --dir node_modules/proto-plugin sync-from-source",
247
289
  "link-source-db": "pnpm --dir node_modules/proto-plugin link-source-db",
@@ -252,7 +294,7 @@ Follow the package README (`node_modules/proto-plugin/README.md`) and implement
252
294
 
253
295
  9. **`.env.example` and `.env.local`** — document and set `SOURCE_PATH` to the source app path I provided. Add comment/screenshot storage vars only if I asked for them in step 5 — the app should run without them, but comments won't persist.
254
296
 
255
- Run `pnpm dev` and confirm `/` shows the empty gallery and the app builds cleanly before continuing.
297
+ Run `pnpm dev` and confirm [http://localhost:1985](http://localhost:1985) shows the empty gallery and the app builds cleanly before continuing.
256
298
 
257
299
  ## 3. Link the source app
258
300
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "proto-plugin",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org"
@@ -33,7 +33,7 @@ SYNC_GLOBS=(
33
33
  # the source version, which the blanket copy overwrites and you must re-apply.
34
34
  # Leave empty if the host does not diverge from source.
35
35
  SYNC_LOCAL_EXTENSIONS=(
36
- # "my-component (local variant + semantic token classes)"
36
+ # "src/app/globals.css (keep @source line; tool theme lives in layout via proto-plugin/styles/globals.css)"
37
37
  )
38
38
 
39
39
  # --- pnpm link-source-db ---------------------------------------------------