proto-plugin 0.1.1 → 0.1.3

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 (109) hide show
  1. package/AGENTS.md +308 -29
  2. package/README.md +48 -6
  3. package/package.json +2 -1
  4. package/scripts/lib/host-config.example.sh +1 -1
  5. package/scripts/verify-prototype-preview-states.mjs +57 -0
  6. package/src/components/platform-ui/controls-panel-select.tsx +4 -3
  7. package/src/components/platform-ui/prototype-tool-dialog.tsx +117 -0
  8. package/src/components/platform-ui/sidebar.tsx +34 -16
  9. package/src/components/prototype-gallery-client.tsx +77 -37
  10. package/src/components/prototypes/design-exploration-variant-preview-shell.tsx +43 -0
  11. package/src/components/prototypes/prototype-brief-field.tsx +33 -0
  12. package/src/components/prototypes/prototype-change-log-panel.tsx +59 -55
  13. package/src/components/prototypes/prototype-comment-provider.tsx +0 -1
  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-chrome.tsx +21 -2
  24. package/src/components/prototypes/prototype-review-sidebar.tsx +7 -37
  25. package/src/components/prototypes/prototype-spec-panel-content.tsx +5 -5
  26. package/src/components/prototypes/prototype-spec-panel-empty-state.tsx +53 -0
  27. package/src/components/prototypes/prototype-state-canvas-overlay.module.scss +20 -47
  28. package/src/components/prototypes/prototype-state-canvas-overlay.tsx +4 -42
  29. package/src/components/prototypes/prototype-variant-explorer.tsx +32 -78
  30. package/src/components/prototypes/prototype-vercel-preview-modal.tsx +4 -3
  31. package/src/components/prototypes/state-map-annotation-tooltip.module.scss +7 -7
  32. package/src/components/shell/prototype-component-library-empty-state.tsx +59 -0
  33. package/src/components/shell/prototype-gallery-nav.tsx +124 -0
  34. package/src/components/shell/prototype-gallery-shell.tsx +45 -13
  35. package/src/components/shell/prototype-link-source-modal.tsx +100 -0
  36. package/src/components/shell/prototype-plugin-update-popup.tsx +74 -0
  37. package/src/components/ui/button.tsx +4 -2
  38. package/src/components/ui/dialog.tsx +44 -5
  39. package/src/components/ui/dropdown-menu.tsx +2 -2
  40. package/src/components/ui/input.tsx +1 -1
  41. package/src/components/ui/label.tsx +1 -1
  42. package/src/components/ui/option-select.tsx +3 -3
  43. package/src/components/ui/scroll-area.tsx +1 -1
  44. package/src/components/ui/separator.tsx +1 -1
  45. package/src/components/ui/slider.tsx +1 -1
  46. package/src/components/ui/switch.tsx +1 -1
  47. package/src/components/ui/tabs.tsx +1 -1
  48. package/src/components/ui/toggle-group.tsx +1 -1
  49. package/src/components/ui/tooltip.tsx +2 -2
  50. package/src/config/create-prototype-component-library-page.tsx +74 -0
  51. package/src/config/create-prototype-gallery-page.tsx +26 -23
  52. package/src/config/create-prototype-host-app.tsx +3 -15
  53. package/src/config/create-prototype-site-layout.tsx +14 -1
  54. package/src/config/create-prototype-state-map-page.tsx +44 -0
  55. package/src/config/prototype-site-layout-client.tsx +23 -6
  56. package/src/config/with-prototype.ts +54 -0
  57. package/src/index.ts +71 -11
  58. package/src/lib/copy-text-with-attachments.ts +35 -0
  59. package/src/lib/format-source-directory-name.ts +12 -0
  60. package/src/lib/platform-ui/sidebar-interactive-cursor.ts +19 -0
  61. package/src/lib/pr-split/build-pr-split-plan-prompt.ts +81 -0
  62. package/src/lib/pr-split/pr-split-highlight.ts +3 -0
  63. package/src/lib/prototype-comments/core/annotation-target.ts +12 -3
  64. package/src/lib/prototype-comments/core/comment-capture-blocked.ts +24 -0
  65. package/src/lib/prototype-comments/core/screenshot.ts +68 -31
  66. package/src/lib/prototype-comments/core/storage.ts +3 -6
  67. package/src/lib/prototype-comments/core/types.ts +2 -0
  68. package/src/lib/prototype-comments/hooks/useLayoutShift.ts +9 -2
  69. package/src/lib/prototype-comments/react/CommentCaptureToolbar.tsx +45 -10
  70. package/src/lib/prototype-comments/react/CommentProvider.tsx +19 -0
  71. package/src/lib/prototype-comments/ui/CommentModeToggleButton.tsx +34 -0
  72. package/src/lib/prototype-comments/ui/CommentsGrid.tsx +8 -16
  73. package/src/lib/prototype-comments/ui/CommentsSidebar.tsx +5 -19
  74. package/src/lib/prototype-comments/ui/capture-styles.module.scss +5 -1
  75. package/src/lib/prototype-comments/ui/comments-grid-card.module.scss +0 -7
  76. package/src/lib/prototypes/build-design-exploration-renderers.ts +44 -0
  77. package/src/lib/prototypes/changelog-description-prompt.ts +58 -0
  78. package/src/lib/prototypes/comment-storage-setup-prompt.ts +124 -0
  79. package/src/lib/prototypes/create-prototype-prompt.ts +96 -0
  80. package/src/lib/prototypes/create-prototype-registry.ts +5 -0
  81. package/src/lib/prototypes/design-exploration-types.ts +261 -10
  82. package/src/lib/prototypes/link-source-prompt.ts +93 -0
  83. package/src/lib/prototypes/populate-component-library-prompt.ts +64 -0
  84. package/src/lib/prototypes/proto-plugin-version.ts +19 -0
  85. package/src/lib/prototypes/prototype-config-types.ts +2 -0
  86. package/src/lib/prototypes/prototype-layout-classes.ts +19 -0
  87. package/src/lib/prototypes/prototype-preview-state-registry.ts +5 -2
  88. package/src/lib/prototypes/prototype-review-context.tsx +4 -4
  89. package/src/lib/prototypes/prototype-state-canvas-constants.ts +1 -1
  90. package/src/lib/prototypes/prototype-state-canvas-types.ts +0 -2
  91. package/src/lib/prototypes/prototype-storage-status.ts +31 -0
  92. package/src/lib/prototypes/sort-prototypes-by-creation-date.ts +43 -0
  93. package/src/lib/prototypes/upgrade-proto-plugin-prompt.ts +40 -0
  94. package/src/lib/prototypes/use-proto-plugin-version-check.ts +67 -0
  95. package/src/lib/prototypes/use-prototype-tool-theme.tsx +1 -1
  96. package/src/lib/prototypes/use-toolbar-hover-panel.ts +12 -1
  97. package/src/lib/prototypes/variant-set-lucide-icon.tsx +24 -0
  98. package/src/lib/tool-portal.ts +24 -0
  99. package/src/lib/use-copy-to-clipboard.ts +7 -13
  100. package/src/server/create-host-api-route.ts +14 -16
  101. package/src/server/create-prototype-api-routes.ts +0 -115
  102. package/src/server/proto-plugin-version-route.ts +145 -0
  103. package/src/server/prototype-storage-status-route.ts +29 -0
  104. package/src/server.ts +2 -8
  105. package/src/styles/globals.css +357 -69
  106. package/src/lib/prototypes/design-exploration-default-storage.ts +0 -68
  107. package/src/lib/prototypes/design-exploration-default.ts +0 -51
  108. package/src/lib/prototypes/use-design-exploration-default.ts +0 -114
  109. package/src/server/redis/prototype-design-exploration-default.ts +0 -32
@@ -0,0 +1,124 @@
1
+ export const COMMENT_STORAGE_NOT_CONFIGURED_MESSAGE =
2
+ "Comment storage is not configured. Set Upstash Redis and Vercel Blob env vars.";
3
+
4
+ export function isCommentStorageSetupError(message: string | null | undefined): boolean {
5
+ if (!message) return false;
6
+ return (
7
+ message.includes("not configured") ||
8
+ message.includes("Upstash Redis") ||
9
+ message.includes("Vercel Blob") ||
10
+ message.includes("KV_REST") ||
11
+ message.includes("BLOB_")
12
+ );
13
+ }
14
+
15
+ export type CommentStorageSetupPromptOptions = {
16
+ vercelProjectName?: string;
17
+ vercelTeamSlug?: string;
18
+ };
19
+
20
+ export function buildCommentStorageSetupPrompt({
21
+ vercelProjectName = "proto-plugin",
22
+ vercelTeamSlug = "maayan-alberts-projects",
23
+ }: CommentStorageSetupPromptOptions = {}): string {
24
+ const upstashTermsUrl = `https://vercel.com/${vercelTeamSlug}/~/integrations/accept-terms/upstash?source=cli`;
25
+
26
+ return [
27
+ "Set up prototype storage (Upstash Redis + Vercel Blob) for this host app. **Do every step yourself** — the user should not run commands manually.",
28
+ "",
29
+ "## Your job",
30
+ "Provision both stores on Vercel, pull env vars locally, restart dev, and confirm Comments + gallery storage work. Only stop to ask the user when a browser step is unavoidable (marketplace terms).",
31
+ "",
32
+ "## Vercel project",
33
+ `- Project: \`${vercelProjectName}\``,
34
+ `- Team: \`${vercelTeamSlug}\``,
35
+ "",
36
+ "## Step 1 — Link the repo (skip if \`.vercel/project.json\` exists)",
37
+ "```bash",
38
+ `npx vercel link --yes --project ${vercelProjectName} --scope ${vercelTeamSlug}`,
39
+ "```",
40
+ "",
41
+ "## Step 2 — Accept Upstash marketplace terms (browser — ask user once)",
42
+ "If `vercel integration add` returns `integration_terms_acceptance_required`, send the user this URL and wait for confirmation before retrying:",
43
+ upstashTermsUrl,
44
+ "",
45
+ "## Step 3 — Install Upstash Redis",
46
+ "```bash",
47
+ "npx vercel integration add upstash/upstash-kv -m primaryRegion=sfo1 -p free -n proto-plugin-redis",
48
+ "```",
49
+ "",
50
+ "Connect to **development**, **preview**, and **production** (default when prompted with `--yes`).",
51
+ "",
52
+ "## Step 4 — Create Vercel Blob store (required)",
53
+ "Gallery screenshots need Blob — not optional.",
54
+ "",
55
+ "```bash",
56
+ "npx vercel blob create-store proto-plugin-blob --access public -y -e development -e preview -e production",
57
+ "```",
58
+ "",
59
+ "If a store already exists, connect it to this project from Vercel → Storage, or use `vercel blob list-stores` and ensure `BLOB_READ_WRITE_TOKEN` is on the project.",
60
+ "",
61
+ "## Step 5 — Verify env vars exist on Vercel",
62
+ "```bash",
63
+ "npx vercel env ls",
64
+ "npx vercel integration list",
65
+ "```",
66
+ "",
67
+ "Required before continuing:",
68
+ "- `KV_REST_API_URL`",
69
+ "- `KV_REST_API_TOKEN`",
70
+ "- `BLOB_READ_WRITE_TOKEN`",
71
+ "",
72
+ "If `vercel env pull` previously only gave `VERCEL_OIDC_TOKEN`, that is CLI auth — not storage. Storage vars appear only after steps 3–4 succeed.",
73
+ "",
74
+ "## Step 6 — Pull env vars locally (required — do not skip)",
75
+ "`.env.local` holds host-only values like `SOURCE_PATH`. **Never** pull into `.env.local` — it overwrites that file.",
76
+ "",
77
+ "Pull development env vars into a separate file:",
78
+ "",
79
+ "```bash",
80
+ "npx vercel env pull .env.development.local -y",
81
+ "```",
82
+ "",
83
+ "Next.js loads both `.env.local` and `.env.development.local` in development.",
84
+ "",
85
+ "After pull, confirm `.env.development.local` contains `KV_REST_*` and `BLOB_READ_WRITE_TOKEN`. Leave `SOURCE_PATH` in `.env.local` untouched.",
86
+ "",
87
+ "## Step 7 — Restart dev and verify",
88
+ "```bash",
89
+ "pnpm dev",
90
+ "```",
91
+ "",
92
+ "Then confirm storage is configured:",
93
+ "",
94
+ "```bash",
95
+ "curl -s http://localhost:1985/api/prototypes/storage-status",
96
+ "```",
97
+ "",
98
+ "Expect:",
99
+ "- `comments.configured: true`",
100
+ "- `gallery.storageConfigured: true`",
101
+ "",
102
+ "Open a prototype → Comments sidebar should load with no setup prompt (empty list is fine).",
103
+ "",
104
+ "## Required env vars (all three)",
105
+ "| Var | Purpose |",
106
+ "| --- | --- |",
107
+ "| `KV_REST_API_URL` | Upstash Redis — comments, change log, gallery metadata |",
108
+ "| `KV_REST_API_TOKEN` | Upstash Redis token |",
109
+ "| `BLOB_READ_WRITE_TOKEN` | Vercel Blob — gallery screenshot uploads |",
110
+ "",
111
+ "Either `KV_*` or `UPSTASH_REDIS_REST_*` works — the app normalizes both.",
112
+ "",
113
+ "## Reference",
114
+ "- Env template: `.env.example`",
115
+ "- Storage status: `packages/prototype/src/server/prototype-storage-status-route.ts`",
116
+ "- Redis client: `packages/prototype/src/server/redis/client.ts`",
117
+ ].join("\n");
118
+ }
119
+
120
+ export function buildCommentStorageSetupCopyText(
121
+ options: CommentStorageSetupPromptOptions = {},
122
+ ): string {
123
+ return buildCommentStorageSetupPrompt(options);
124
+ }
@@ -0,0 +1,96 @@
1
+ export function suggestPrototypeSlug(title: string): string {
2
+ return title
3
+ .trim()
4
+ .toLowerCase()
5
+ .replace(/[^a-z0-9]+/g, "-")
6
+ .replace(/^-+|-+$/g, "");
7
+ }
8
+
9
+ export function buildCreatePrototypePrompt({
10
+ title,
11
+ description,
12
+ origin = "http://localhost:3003",
13
+ }: {
14
+ title: string;
15
+ description: string;
16
+ origin?: string;
17
+ }): string {
18
+ const trimmedTitle = title.trim();
19
+ const trimmedDescription = description.trim();
20
+ const suggestedSlug = suggestPrototypeSlug(trimmedTitle);
21
+ const galleryUrl = `${origin}/`;
22
+
23
+ const lines = [
24
+ `Create a new prototype: "${trimmedTitle}".`,
25
+ "",
26
+ "## Brief",
27
+ `- Title: ${trimmedTitle}`,
28
+ `- Description: ${trimmedDescription}`,
29
+ `- Suggested slug: \`${suggestedSlug || "my-prototype"}\` (pick a unique kebab-case slug if taken)`,
30
+ "",
31
+ "## Source app",
32
+ "Draw from the **source application** (linked via `SOURCE_PATH` / `pnpm link-source`) — not from other prototypes in this gallery. Replicate the page or feature named in the brief **exactly**: layout, spacing, typography, components, empty/loading/error states, and interactions. Match the source side-by-side; do not approximate or invent alternate UI.",
33
+ "",
34
+ "## Gallery",
35
+ galleryUrl,
36
+ ];
37
+
38
+ lines.push(
39
+ "",
40
+ "## Preview states (required — do not skip)",
41
+ "Before building UI, list every distinct mode reviewers must open directly from the State picker and `/prototypes/<slug>/states` map. Include modes implied by the brief and source page, for example:",
42
+ "- Primary tabs, steps, or wizard stages",
43
+ "- Empty, loading, and error branches",
44
+ "- Filter bars, preset filters, active filter chips, and add-filter popovers",
45
+ "- Modals, drawers, and overlay branches",
46
+ "- View toggles (list vs calendar, grid vs table, etc.)",
47
+ "",
48
+ "Each mode gets one registry entry in `*-preview-states.ts` with matching `liveState` in `createLiveStateForPreview`. Wire the full stack in the same pass as the UI — not as a follow-up.",
49
+ "",
50
+ "## Instructions",
51
+ "Follow AGENTS.md → New prototype checklist and **Live state & preview states (mandatory)**.",
52
+ "",
53
+ "1. Find the source page/feature in the linked source app, read it fully, and confirm side-by-side visual parity before finishing.",
54
+ "2. Create `src/prototypes/<slug>/` with a default export page component.",
55
+ "3. Add `component-ids.ts` — exhaustive static id list + `dynamicPrefixes`.",
56
+ "4. Register in `prototype.config.ts` with `componentRegistry`.",
57
+ "5. Wrap every UI component in `PrototypeComponent` / `PrototypeControl`.",
58
+ "6. Define typed `liveState`, wire `usePrototypeComments(liveState, onRestore)`, and derive all preview-relevant UI from `liveState`.",
59
+ "7. Add preview states in the same change (mandatory for feature pages — see **Preview states** above):",
60
+ " - `src/prototypes/<slug>/_components/<slug>-preview-states.ts` — `definePreviewStateRegistry`, `createLiveStateForPreview`, one entry per mode",
61
+ " - `src/prototypes/<slug>/_components/<slug>-preview-state-select.tsx` — wire `ControlsPanelSelect` with `appearance=\"menuList\"` into the review toolbar via `setTweaksContent` (no floating mini-pill on the page)",
62
+ " - `src/prototypes/<slug>/_components/<slug>-state-canvas-config.tsx` — wireframes from the same registry",
63
+ " - `src/prototypes/<slug>/state-map-page.tsx` — full-page state map at `/prototypes/<slug>/states`",
64
+ " - `stateMapComponent` on the prototype entry in `prototype.config.ts`",
65
+ " - In `index.tsx`: `review.setStateCanvasConfig(build…StateCanvasConfig(onStateSelect))` in an effect — pass only the select handler, not `liveState` or the current preview mode (state map is stateless; always light mode)",
66
+ "8. Run `pnpm verify:prototype-ids` and `pnpm verify:prototype-preview-states`.",
67
+ "9. Capture a screenshot under `public/prototypes/screenshots/`; share via `pnpm share-prototype <slug>`.",
68
+ "",
69
+ "Reference: `src/prototypes/booking-flow/` (`index.tsx`, `*-preview-states.ts`, `*-preview-state-select.tsx`, `*-state-canvas-config.tsx`, `state-map-page.tsx`, `prototype.config.ts` → `stateMapComponent`).",
70
+ );
71
+
72
+ return lines.join("\n");
73
+ }
74
+
75
+ export function buildCreatePrototypeCopyText({
76
+ title,
77
+ description,
78
+ origin = "http://localhost:3003",
79
+ }: {
80
+ title: string;
81
+ description: string;
82
+ origin?: string;
83
+ }): string {
84
+ const trimmedTitle = title.trim();
85
+ const trimmedDescription = description.trim();
86
+ const brief = [`Title: ${trimmedTitle}`, `Description: ${trimmedDescription}`].join(
87
+ "\n",
88
+ );
89
+ const prompt = buildCreatePrototypePrompt({
90
+ title: trimmedTitle,
91
+ description: trimmedDescription,
92
+ origin,
93
+ });
94
+
95
+ return `${brief}\n\n---\n\n${prompt}`;
96
+ }
@@ -40,6 +40,11 @@ export function createPrototypeRegistry(config: PrototypeConfig) {
40
40
  ?.component;
41
41
  },
42
42
 
43
+ getPrototypeStateMapComponent(slug: string) {
44
+ return requireConfig().prototypes.find((prototype) => prototype.slug === slug)
45
+ ?.stateMapComponent;
46
+ },
47
+
43
48
  getPrototypeComponentRegistryForSlug(
44
49
  slug: string,
45
50
  ): PrototypeComponentRegistry | undefined {
@@ -1,14 +1,16 @@
1
1
  import type { ReactNode } from "react";
2
2
 
3
+ export type DesignExplorationRationale = {
4
+ good: string;
5
+ bad: string;
6
+ };
7
+
3
8
  export type DesignExplorationVariantOption<TVariant extends string> = {
4
9
  value: TVariant;
5
10
  label: string;
6
11
  hint?: string;
7
- };
8
-
9
- export type DesignExplorationRationale = {
10
- good: string;
11
- bad: string;
12
+ /** One-line pros/cons shown under the variant title in the design brief. */
13
+ rationale?: DesignExplorationRationale;
12
14
  };
13
15
 
14
16
  export type DesignExplorationBrief = {
@@ -47,26 +49,82 @@ export type DesignExplorationVariantsSection = {
47
49
  description?: string;
48
50
  };
49
51
 
50
- /** Persists to KV via `storageKeyPrefix` use `useDesignExplorationDefault`. */
52
+ /** Pre-exploration UI always pinned at the bottom of the variant list. */
53
+ export type DesignExplorationBaselineOption<TVariant extends string> = {
54
+ value: TVariant;
55
+ /** Shown in the brief and tabs. Default: "Original". */
56
+ label?: string;
57
+ hint?: string;
58
+ rationale?: DesignExplorationRationale;
59
+ };
60
+
51
61
  export type DesignExplorationConfig<TVariant extends string> = {
52
62
  componentIdPrefix: string;
53
63
  variantTabsIdPrefix: string;
54
64
  storageKeyPrefix: string;
55
65
  variant: TVariant;
56
66
  onVariantChange: (variant: TVariant) => void;
67
+ /** Layout directions being explored — newest first. Excludes baseline. */
57
68
  options: DesignExplorationVariantOption<TVariant>[];
58
- rationale: Record<TVariant, DesignExplorationRationale>;
59
69
  renderers: Record<TVariant, () => ReactNode>;
60
70
  brief: DesignExplorationBrief;
61
71
  context?: DesignExplorationContext;
62
72
  mobbin?: DesignExplorationMobbin;
63
73
  variantsSection?: DesignExplorationVariantsSection;
74
+ /**
75
+ * UI state before this exploration was added. Rendered last in the brief
76
+ * and tabs; deduped from `options` when the same value appears there.
77
+ */
78
+ baseline: DesignExplorationBaselineOption<TVariant>;
79
+ /**
80
+ * Default variant stored in the codebase (e.g. `DEFAULT_*_VARIANT`). Marks
81
+ * the "Default" option in the design brief. Falls back to the first option.
82
+ */
83
+ defaultVariant?: TVariant;
64
84
  /** Label for prev/next variant tab buttons. Default: "variant". */
65
85
  variantTabAriaLabel?: string;
66
- /** Path to design exploration config — included in reset-brief copy prompt. */
86
+ /** Path to design exploration config — included in copy prompts. */
67
87
  briefConfigFilePath?: string;
68
88
  };
69
89
 
90
+ export const DESIGN_EXPLORATION_BASELINE_LABEL = "Original";
91
+
92
+ export function resolveDesignExplorationBaselineOption<
93
+ TVariant extends string,
94
+ >(
95
+ baseline: DesignExplorationBaselineOption<TVariant>,
96
+ ): DesignExplorationVariantOption<TVariant> {
97
+ return {
98
+ value: baseline.value,
99
+ label: baseline.label ?? DESIGN_EXPLORATION_BASELINE_LABEL,
100
+ hint: baseline.hint,
101
+ rationale: baseline.rationale,
102
+ };
103
+ }
104
+
105
+ /** Exploration options only — baseline value removed when duplicated. */
106
+ export function getDesignExplorationVariantOptions<
107
+ TVariant extends string,
108
+ >(
109
+ options: DesignExplorationVariantOption<TVariant>[],
110
+ baseline: DesignExplorationBaselineOption<TVariant>,
111
+ ): DesignExplorationVariantOption<TVariant>[] {
112
+ return options.filter((option) => option.value !== baseline.value);
113
+ }
114
+
115
+ /** Exploration options plus baseline pinned at the bottom (for tabs / cycling). */
116
+ export function getDesignExplorationDisplayOptions<
117
+ TVariant extends string,
118
+ >(
119
+ options: DesignExplorationVariantOption<TVariant>[],
120
+ baseline: DesignExplorationBaselineOption<TVariant>,
121
+ ): DesignExplorationVariantOption<TVariant>[] {
122
+ return [
123
+ ...getDesignExplorationVariantOptions(options, baseline),
124
+ resolveDesignExplorationBaselineOption(baseline),
125
+ ];
126
+ }
127
+
70
128
  /** Plain-text copy of the overview fields as shown in the design brief modal. */
71
129
  export function buildBriefOverviewCopyText(
72
130
  title: string,
@@ -84,11 +142,13 @@ export function buildCreateDesignExplorationPrompt({
84
142
  existingExplorations = [],
85
143
  origin = "http://localhost:3003",
86
144
  briefText,
145
+ includeMobbin = true,
87
146
  }: {
88
147
  slug: string;
89
148
  existingExplorations?: Array<{ id: string; label: string }>;
90
149
  origin?: string;
91
150
  briefText?: string;
151
+ includeMobbin?: boolean;
92
152
  }): string {
93
153
  const prototypeUrl = `${origin}/prototypes/${slug}`;
94
154
  const variantSetsRegistrar = `src/prototypes/${slug}/_components/*-variant-sets.tsx`;
@@ -122,6 +182,27 @@ export function buildCreateDesignExplorationPrompt({
122
182
  "## Prototype",
123
183
  `- Slug: \`${slug}\``,
124
184
  `- Preview: ${prototypeUrl}`,
185
+ );
186
+
187
+ if (includeMobbin) {
188
+ lines.push(
189
+ "",
190
+ "## Mobbin references",
191
+ "Before building variants, pull real-world reference screens from Mobbin. Use the Mobbin MCP `search_screens` tool when available; otherwise search on mobbin.com.",
192
+ "",
193
+ "1. Search for **3–5 screens** that match the brief and the distinct layout directions you plan to explore. Prefer specific UI descriptions (e.g. \"settings page with profile photo and bio field\") over vague style words.",
194
+ "2. Pick references that clarify layout, hierarchy, and copy patterns — not just visual polish.",
195
+ "3. In `*-design-exploration-config.tsx`, add a `mobbin` block:",
196
+ " - `references`: `{ id, appName, imageUrl, mobbinUrl, relevance, variantHint? }[]` — use each screen UUID as `id`",
197
+ " - `imagePathForReference`: `(id) => \\`/prototypes/mobbin-references/${id}.webp\\``",
198
+ "4. Register `mobbin-inspiration-gallery` and `mobbin-inspiration-gallery.` in `component-ids.ts` (or pass a custom `mobbinGalleryId` to `PrototypeVariantExplorer`).",
199
+ "5. Download local assets: `pnpm download-mobbin-references -- --ids=<comma-separated-ids>`",
200
+ "",
201
+ "Reference: `src/prototypes/proto-partner-page/_components/invite-copy-design-exploration-config.tsx`.",
202
+ );
203
+ }
204
+
205
+ lines.push(
125
206
  "",
126
207
  "## Instructions",
127
208
  "Follow AGENTS.md → Design exploration. Do not register a new prototype slug — add a design exploration variant set on this page.",
@@ -129,7 +210,12 @@ export function buildCreateDesignExplorationPrompt({
129
210
  "1. Add `{ id, label, previewMode? }` to the variant sets registrar (e.g. `" +
130
211
  variantSetsRegistrar +
131
212
  "`).",
132
- "2. Create `*-design-exploration-config.tsx` with options, rationale, renderers, and brief defaults.",
213
+ " - Give this exploration a **distinct Lucide icon** in the review sidebar: add a `{ test, icon }` rule to `packages/prototype/src/lib/prototypes/variant-set-lucide-icon.tsx` that matches its id or label. Place the rule **before** broad matchers (e.g. `/mobile/i`) so it wins. Pick an icon that fits the topic and avoid reusing icons already assigned to other explorations on this prototype — do not leave new explorations on the default `LayoutGrid` fallback or the generic `Smartphone` icon shared by unrelated mobile explorations.",
214
+ `2. Create \`*-design-exploration-config.tsx\` with options, renderers, brief defaults, baseline,${includeMobbin ? " and the Mobbin references above" : ""}.`,
215
+ " - `baseline`: the UI before this exploration existed — `{ value, label?, hint? }`, pinned at the bottom of the variant list.",
216
+ " - `options`: layout directions to compare — exclude the baseline value when possible.",
217
+ " - Build renderers with `buildDesignExplorationRenderers(options, (variant) => …, baseline)` — one preview per option plus baseline.",
218
+ " - Wrap overlay-style variants in `DesignExplorationVariantPreviewShell` with `layout=\"overlay\"` for sidebar previews.",
133
219
  "3. Create `*-variant-toggle.tsx` mounting `PrototypeVariantExplorer` with matching `variantSet={{ id, label }}`.",
134
220
  "4. Register static ids + any `dynamicPrefixes` in `src/prototypes/" +
135
221
  slug +
@@ -149,11 +235,13 @@ export function buildCreateDesignExplorationCopyText({
149
235
  existingExplorations = [],
150
236
  origin = "http://localhost:3003",
151
237
  briefText,
238
+ includeMobbin = true,
152
239
  }: {
153
240
  slug: string;
154
241
  existingExplorations?: Array<{ id: string; label: string }>;
155
242
  origin?: string;
156
243
  briefText: string;
244
+ includeMobbin?: boolean;
157
245
  }): string {
158
246
  const trimmedBrief = briefText.trim();
159
247
  const prompt = buildCreateDesignExplorationPrompt({
@@ -161,6 +249,141 @@ export function buildCreateDesignExplorationCopyText({
161
249
  existingExplorations,
162
250
  origin,
163
251
  briefText: trimmedBrief,
252
+ includeMobbin,
253
+ });
254
+
255
+ return `${trimmedBrief}\n\n---\n\n${prompt}`;
256
+ }
257
+
258
+ export function buildMoreDesignExplorationVariantsPrompt({
259
+ slug,
260
+ explorationLabel,
261
+ explorationId,
262
+ options,
263
+ brief,
264
+ configFilePath,
265
+ origin = "http://localhost:3003",
266
+ briefText,
267
+ multipleVersions = true,
268
+ }: {
269
+ slug: string;
270
+ explorationLabel: string;
271
+ explorationId?: string;
272
+ options: Array<{ value: string; label: string; hint?: string }>;
273
+ brief: DesignExplorationBrief;
274
+ configFilePath?: string;
275
+ origin?: string;
276
+ briefText?: string;
277
+ /** When false, prompt asks for one targeted derivative variant. */
278
+ multipleVersions?: boolean;
279
+ }): string {
280
+ const prototypeUrl = `${origin}/prototypes/${slug}`;
281
+ const configTarget = configFilePath ?? "the design exploration config file";
282
+ const trimmedBrief = briefText?.trim() ?? "";
283
+
284
+ const lines = [
285
+ `Add new layout variants to the "${explorationLabel}" design exploration on the "${slug}" prototype.`,
286
+ "",
287
+ "## Brief",
288
+ ];
289
+
290
+ if (trimmedBrief) {
291
+ lines.push(trimmedBrief);
292
+ } else if (multipleVersions) {
293
+ lines.push(
294
+ "Describe the new directions to explore:",
295
+ "- What to try:",
296
+ "- Target count: 3–5 new variants",
297
+ );
298
+ } else {
299
+ lines.push(
300
+ "Describe the variation to explore:",
301
+ "- What to try:",
302
+ "- Target count: 1 new variant",
303
+ );
304
+ }
305
+
306
+ lines.push(
307
+ "",
308
+ "## Exploration context",
309
+ `- Label: ${explorationLabel}`,
310
+ );
311
+
312
+ if (explorationId) {
313
+ lines.push(`- Variant set id: \`${explorationId}\``);
314
+ }
315
+
316
+ lines.push(
317
+ `- Overview title: ${brief.titleDefault}`,
318
+ `- Overview description: ${brief.descriptionDefault}`,
319
+ "",
320
+ "## Existing variants",
321
+ );
322
+
323
+ for (const option of options) {
324
+ const hint = option.hint ? ` — ${option.hint}` : "";
325
+ lines.push(`- ${option.label} (\`${option.value}\`)${hint}`);
326
+ }
327
+
328
+ lines.push(
329
+ "",
330
+ "## Prototype",
331
+ `- Slug: \`${slug}\``,
332
+ `- Config: \`${configTarget}\``,
333
+ `- Preview: ${prototypeUrl}`,
334
+ "",
335
+ "## Instructions",
336
+ "Follow AGENTS.md → Design exploration → Iterating on variants.",
337
+ "",
338
+ multipleVersions
339
+ ? "Supply **3–5 new variant options** unless the brief specifies a different count."
340
+ : "Add **one new derivative variant** unless the brief specifies otherwise.",
341
+ "",
342
+ multipleVersions
343
+ ? "When modifying an existing option, add **new variants** (do not replace in place)."
344
+ : "When modifying an existing option, add a **single new variant** (do not replace in place). Keep all existing variants.",
345
+ "",
346
+ "1. Prepend new entries to the top of `*_VARIANT_OPTIONS` so newest explorations appear first.",
347
+ "2. Add matching renderers via `buildDesignExplorationRenderers` and any new component ids.",
348
+ "3. Keep existing variants unless the brief explicitly asks to remove one.",
349
+ "4. Run `pnpm verify:prototype-ids`.",
350
+ );
351
+
352
+ return lines.join("\n");
353
+ }
354
+
355
+ export function buildMoreDesignExplorationVariantsCopyText({
356
+ slug,
357
+ explorationLabel,
358
+ explorationId,
359
+ options,
360
+ brief,
361
+ configFilePath,
362
+ origin = "http://localhost:3003",
363
+ briefText,
364
+ multipleVersions = true,
365
+ }: {
366
+ slug: string;
367
+ explorationLabel: string;
368
+ explorationId?: string;
369
+ options: Array<{ value: string; label: string; hint?: string }>;
370
+ brief: DesignExplorationBrief;
371
+ configFilePath?: string;
372
+ origin?: string;
373
+ briefText: string;
374
+ multipleVersions?: boolean;
375
+ }): string {
376
+ const trimmedBrief = briefText.trim();
377
+ const prompt = buildMoreDesignExplorationVariantsPrompt({
378
+ slug,
379
+ explorationLabel,
380
+ explorationId,
381
+ options,
382
+ brief,
383
+ configFilePath,
384
+ origin,
385
+ briefText: trimmedBrief,
386
+ multipleVersions,
164
387
  });
165
388
 
166
389
  return `${trimmedBrief}\n\n---\n\n${prompt}`;
@@ -187,9 +410,37 @@ export function buildResetBriefDefaultsPrompt({
187
410
  ].join("\n");
188
411
  }
189
412
 
413
+ /**
414
+ * Prompt that asks an agent to set the codebase-stored default variant. Copied
415
+ * by the "Make default" control in the design brief — the default lives in the
416
+ * config file (e.g. `DEFAULT_*_VARIANT`), not in KV storage.
417
+ */
418
+ export function buildSetDefaultVariantPrompt({
419
+ variantLabel,
420
+ variantValue,
421
+ configFilePath,
422
+ }: {
423
+ variantLabel: string;
424
+ variantValue: string;
425
+ configFilePath?: string;
426
+ }): string {
427
+ const target = configFilePath ?? "the design exploration config file";
428
+
429
+ return [
430
+ `Make "${variantLabel}" the default design exploration variant.`,
431
+ "",
432
+ `In ${target}, update the exported default variant constant (e.g. \`DEFAULT_*_VARIANT\`) so it equals:`,
433
+ "",
434
+ variantValue,
435
+ "",
436
+ 'This constant is the codebase-stored default the prototype renders first, and the option marked "Default" in the design brief. Keep it as one of the values in the variant options list.',
437
+ ].join("\n");
438
+ }
439
+
190
440
  /**
191
441
  * Prepend new entries to the top of *_VARIANT_OPTIONS so they appear first in the
192
- * design-brief scroll view (see AGENTS.md). Default variant is stored in KV.
442
+ * design-brief scroll view (see AGENTS.md). The default variant is stored in the
443
+ * codebase config (`defaultVariant`), not KV.
193
444
  */
194
445
  export function getAdjacentDesignExplorationVariant<TVariant extends string>(
195
446
  options: DesignExplorationVariantOption<TVariant>[],
@@ -0,0 +1,93 @@
1
+ export function buildLinkSourcePrompt({
2
+ sourceInput,
3
+ description,
4
+ currentSourcePath,
5
+ referencePaths = [],
6
+ }: {
7
+ sourceInput?: string;
8
+ description?: string;
9
+ currentSourcePath?: string;
10
+ referencePaths?: string[];
11
+ } = {}): string {
12
+ const trimmedInput = sourceInput?.trim();
13
+ const trimmedDescription = description?.trim();
14
+ const trimmedCurrent = currentSourcePath?.trim();
15
+ const isUpdate = Boolean(trimmedCurrent);
16
+
17
+ const lines = [
18
+ isUpdate
19
+ ? "Update the linked source application for this prototype host app."
20
+ : "Link the source application for this prototype host app.",
21
+ "",
22
+ "## Goal",
23
+ "Set `SOURCE_PATH` in `.env.local` and run `pnpm link-source` so the `source/` symlink points at the real product repo.",
24
+ ];
25
+
26
+ if (trimmedCurrent) {
27
+ lines.push("", "## Current source", `- \`SOURCE_PATH\`: \`${trimmedCurrent}\``);
28
+ }
29
+
30
+ lines.push("", "## Desired source");
31
+
32
+ if (trimmedInput) {
33
+ lines.push(`- ${trimmedInput}`);
34
+ lines.push(
35
+ "",
36
+ "Resolve this to a filesystem path (folder name, relative path like `../cal.diy`, or absolute path) before writing `.env.local`.",
37
+ );
38
+ } else {
39
+ lines.push("- (Specify folder name, path, or description above before copying.)");
40
+ }
41
+
42
+ lines.push(
43
+ "",
44
+ "## Clarification",
45
+ "If the input is vague, matches multiple folders on disk, or you cannot confidently identify the correct repo from the description and filesystem, ask the user a clarifying question before changing anything. Do not guess — confirm the exact path first.",
46
+ );
47
+
48
+ if (trimmedDescription) {
49
+ lines.push("", "## Notes", trimmedDescription);
50
+ }
51
+
52
+ if (referencePaths.length > 0) {
53
+ lines.push(
54
+ "",
55
+ "## Reference screenshots",
56
+ "Use these files for additional context:",
57
+ );
58
+ for (const referencePath of referencePaths) {
59
+ lines.push(`- \`${referencePath}\``);
60
+ }
61
+ }
62
+
63
+ lines.push(
64
+ "",
65
+ "## Steps",
66
+ "1. Confirm the correct source path. If it is still unclear after checking the description and filesystem, ask the user before proceeding.",
67
+ "2. Set or update `SOURCE_PATH` in `.env.local` at the host repo root (see `.env.example`).",
68
+ "3. Run `pnpm link-source` from the host repo to create or refresh the `source/` symlink.",
69
+ "4. Restart the dev server if it is already running so env changes are picked up.",
70
+ "5. Confirm the gallery sidebar footer shows the correct source directory name.",
71
+ );
72
+
73
+ return lines.join("\n");
74
+ }
75
+
76
+ export function buildLinkSourceCopyText({
77
+ sourcePath,
78
+ description,
79
+ currentSourcePath,
80
+ referencePaths = [],
81
+ }: {
82
+ sourcePath?: string;
83
+ description?: string;
84
+ currentSourcePath?: string;
85
+ referencePaths?: string[];
86
+ } = {}): string {
87
+ return buildLinkSourcePrompt({
88
+ sourceInput: sourcePath,
89
+ description,
90
+ currentSourcePath,
91
+ referencePaths,
92
+ });
93
+ }