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
@@ -1,19 +1,27 @@
1
1
  "use client";
2
2
 
3
3
  import { Button } from "@prototype/components/ui/button";
4
- import { PrototypeComponent } from "@prototype/components/prototypes/prototype-target";
4
+ import { Switch } from "@prototype/components/ui/switch";
5
+ import {
6
+ PrototypeComponent,
7
+ usePrototypeSlug,
8
+ } from "@prototype/components/prototypes/prototype-target";
9
+ import { PrototypeBriefTextarea } from "@prototype/components/prototypes/prototype-brief-field";
5
10
  import { PrototypeDesignContextPanel } from "@prototype/components/prototypes/prototype-design-context-panel";
6
11
  import { PrototypeMobbinGallery } from "@prototype/components/prototypes/prototype-mobbin-gallery";
7
- import { PrototypeVariantRationalePanel } from "@prototype/components/prototypes/prototype-variant-rationale-panel";
8
12
  import {
13
+ buildMoreDesignExplorationVariantsCopyText,
9
14
  buildResetBriefDefaultsPrompt,
15
+ buildSetDefaultVariantPrompt,
16
+ getDesignExplorationDisplayOptions,
17
+ getDesignExplorationVariantOptions,
18
+ resolveDesignExplorationBaselineOption,
19
+ type DesignExplorationBaselineOption,
10
20
  type DesignExplorationBrief,
11
21
  type DesignExplorationContext,
12
22
  type DesignExplorationMobbin,
13
- type DesignExplorationRationale,
14
23
  type DesignExplorationVariantOption,
15
24
  type DesignExplorationVariantsSection,
16
- type MobbinReference,
17
25
  } from "@prototype/lib/prototypes/design-exploration-types";
18
26
  import {
19
27
  usePersistedLocalBoolean,
@@ -23,61 +31,6 @@ import useCopyToClipboard from "@prototype/lib/use-copy-to-clipboard";
23
31
  import { cn } from "@prototype/lib/utils";
24
32
  import { useEffect, useState } from "react";
25
33
 
26
- function scrollToMobbinReference(
27
- componentIdPrefix: string,
28
- referenceId: string,
29
- ) {
30
- const el = document.querySelector(
31
- `[data-prototype-target*="${componentIdPrefix}.mobbin-reference.${referenceId}"]`,
32
- );
33
- if (el) {
34
- el.scrollIntoView({ behavior: "smooth", block: "nearest" });
35
- }
36
- }
37
-
38
- function VariantHint({
39
- hint,
40
- references,
41
- componentIdPrefix,
42
- }: {
43
- hint: string;
44
- references: MobbinReference[];
45
- componentIdPrefix: string;
46
- }) {
47
- const parts = hint.split(/,\s*/);
48
-
49
- return (
50
- <>
51
- {parts.map((part, i) => {
52
- const trimmed = part.trim();
53
- const matchedRef = references.find(
54
- (r) => r.appName.toLowerCase() === trimmed.toLowerCase(),
55
- );
56
- const isLast = i === parts.length - 1;
57
-
58
- return (
59
- <span key={i}>
60
- {matchedRef ? (
61
- <button
62
- type="button"
63
- onClick={() =>
64
- scrollToMobbinReference(componentIdPrefix, matchedRef.id)
65
- }
66
- className="cursor-pointer underline decoration-dotted underline-offset-2 transition-colors duration-200 ease hover:text-foreground"
67
- >
68
- {trimmed}
69
- </button>
70
- ) : (
71
- trimmed
72
- )}
73
- {!isLast ? ", " : ""}
74
- </span>
75
- );
76
- })}
77
- </>
78
- );
79
- }
80
-
81
34
  const MODAL_VARIANT_PREVIEW_SCALE = 0.75;
82
35
  const SIDEBAR_VARIANT_PREVIEW_SCALE = 0.42;
83
36
 
@@ -89,12 +42,8 @@ const MODAL_OVERVIEW_DESCRIPTION_FIELD_CLASS =
89
42
 
90
43
  const MODAL_GALLERY_SECTION_TITLE_CLASS = "tool-heading";
91
44
 
92
- const MODAL_GALLERY_SECTION_DESC_CLASS = "tool-body max-w-2xl";
93
-
94
45
  const MODAL_GALLERY_CARD_TITLE_CLASS = "text-foreground font-medium";
95
46
 
96
- const MODAL_GALLERY_CARD_BODY_CLASS = "text-sm text-muted-foreground leading-relaxed";
97
-
98
47
  const SIDEBAR_OVERVIEW_TITLE_FIELD_CLASS =
99
48
  "text-sm text-foreground field-sizing-content w-full min-h-0 resize-none border-0 bg-transparent p-0 font-medium leading-snug shadow-none outline-none focus-visible:ring-0 focus-visible:ring-offset-0";
100
49
 
@@ -103,12 +52,8 @@ const SIDEBAR_OVERVIEW_DESCRIPTION_FIELD_CLASS =
103
52
 
104
53
  const SIDEBAR_GALLERY_SECTION_TITLE_CLASS = "tool-section-label";
105
54
 
106
- const SIDEBAR_GALLERY_SECTION_DESC_CLASS = "tool-body";
107
-
108
55
  const SIDEBAR_GALLERY_CARD_TITLE_CLASS = "text-sm text-foreground font-medium";
109
56
 
110
- const SIDEBAR_GALLERY_CARD_BODY_CLASS = "text-sm text-muted-foreground leading-relaxed";
111
-
112
57
  export type PrototypeDesignBriefPanelLayout = "default" | "sidebar";
113
58
 
114
59
  export type PrototypeDesignBriefPanelProps<TVariant extends string> = {
@@ -122,57 +67,62 @@ export type PrototypeDesignBriefPanelProps<TVariant extends string> = {
122
67
  variant: TVariant;
123
68
  onVariantChange: (variant: TVariant) => void;
124
69
  options: DesignExplorationVariantOption<TVariant>[];
125
- rationale: Record<TVariant, DesignExplorationRationale>;
126
70
  renderers: Record<TVariant, () => React.ReactNode>;
127
- /** Shown with a "Default" pill when this option matches the KV default. */
71
+ /** Pre-exploration UI pinned at the bottom of the variant list. */
72
+ baseline: DesignExplorationBaselineOption<TVariant>;
73
+ /**
74
+ * Marked "Default" in the brief — the codebase-stored default variant
75
+ * (`config.defaultVariant`). Other options offer a "Make default" prompt.
76
+ */
128
77
  defaultVariantValue?: TVariant;
129
- /** Persists the selected default variant to KV storage. */
130
- onSetDefaultVariant?: (value: TVariant) => void;
131
- /** Path to design exploration config — included in overview copy prompt. */
78
+ /** Path to design exploration config included in copy prompts. */
132
79
  briefConfigFilePath?: string;
80
+ /** Sidebar prompt copy — label from variant set or brief title. */
81
+ explorationLabel?: string;
82
+ /** Sidebar prompt copy — variant set id when registered. */
83
+ explorationId?: string;
133
84
  layout?: PrototypeDesignBriefPanelLayout;
134
85
  className?: string;
135
86
  };
136
87
 
137
- const VARIANT_DEFAULT_CONTROL_CLASS =
138
- "h-6 shrink-0 border border-border-solid bg-bg-top px-2 text-[11px] font-medium text-foreground shadow-xs hover:border-border-medium hover:bg-bg-layered";
139
-
140
- const VARIANT_DEFAULT_ACTIVE_CLASS =
141
- "border-[color-mix(in_srgb,var(--primary)_35%,var(--border-solid))] bg-[var(--tool-chrome-blue-highlight)] text-primary-dark hover:border-[color-mix(in_srgb,var(--primary)_35%,var(--border-solid))] hover:bg-[var(--tool-chrome-blue-highlight)]";
142
-
143
88
  const SIDEBAR_OVERVIEW_COPY_BUTTON_CLASS =
144
- "text-[var(--tool-chrome-icon)] hover:text-[var(--tool-chrome-icon-hover)] inline-flex size-7 shrink-0 items-center justify-center rounded-sm border-0 bg-transparent p-0 transition-colors duration-200 ease outline-none focus:outline-hidden focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-offset-0 disabled:pointer-events-none disabled:opacity-40 motion-reduce:transition-none [&_svg]:pointer-events-none [&_svg]:shrink-0";
89
+ "cursor-pointer text-[var(--tool-chrome-icon)] hover:text-[var(--tool-chrome-icon-hover)] inline-flex size-7 shrink-0 items-center justify-center rounded-sm border-0 bg-transparent p-0 transition-colors duration-200 ease outline-none focus:outline-hidden focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-offset-0 disabled:pointer-events-none disabled:opacity-40 motion-reduce:transition-none [&_svg]:pointer-events-none [&_svg]:shrink-0";
145
90
 
146
- function VariantRationaleSection<TVariant extends string>({
147
- componentIdPrefix,
91
+ function MakeDefaultButton({
92
+ componentId,
93
+ optionLabel,
148
94
  optionValue,
149
- storageKeyPrefix,
150
- rationale,
151
- layout,
152
- bodyClassName,
95
+ configFilePath,
153
96
  }: {
154
- componentIdPrefix: string;
155
- optionValue: TVariant;
156
- storageKeyPrefix: string;
157
- rationale: DesignExplorationRationale;
158
- layout: PrototypeDesignBriefPanelLayout;
159
- bodyClassName: string;
97
+ componentId: string;
98
+ optionLabel: string;
99
+ optionValue: string;
100
+ configFilePath?: string;
160
101
  }) {
161
- const { value: expanded, updateValue: updateExpanded } =
162
- usePersistedLocalBoolean(
163
- `${storageKeyPrefix}-variant-rationale-expanded-${optionValue}`,
164
- false,
165
- );
102
+ const { copy, isCopied } = useCopyToClipboard();
166
103
 
167
104
  return (
168
- <PrototypeVariantRationalePanel
169
- componentId={`${componentIdPrefix}.overview-option.${optionValue}.rationale`}
170
- rationale={rationale}
171
- expanded={expanded}
172
- onExpandedChange={updateExpanded}
173
- layout={layout}
174
- bodyClassName={bodyClassName}
175
- />
105
+ <PrototypeComponent id={componentId}>
106
+ <Button
107
+ type="button"
108
+ variant="chrome"
109
+ size="sm"
110
+ className="h-6 shrink-0 rounded px-2 text-[11px]"
111
+ onClick={(event) => {
112
+ event.stopPropagation();
113
+ copy(
114
+ buildSetDefaultVariantPrompt({
115
+ variantLabel: optionLabel,
116
+ variantValue: optionValue,
117
+ configFilePath,
118
+ }),
119
+ );
120
+ }}
121
+ aria-label={`Copy prompt to make ${optionLabel} the default`}
122
+ >
123
+ {isCopied ? "Copied prompt" : "Make default"}
124
+ </Button>
125
+ </PrototypeComponent>
176
126
  );
177
127
  }
178
128
 
@@ -187,15 +137,17 @@ export function PrototypeDesignBriefPanel<TVariant extends string>({
187
137
  variant,
188
138
  onVariantChange,
189
139
  options,
190
- rationale,
191
140
  renderers,
141
+ baseline,
192
142
  defaultVariantValue,
193
- onSetDefaultVariant,
194
143
  briefConfigFilePath,
144
+ explorationLabel,
145
+ explorationId,
195
146
  layout = "default",
196
147
  className,
197
148
  }: PrototypeDesignBriefPanelProps<TVariant>) {
198
149
  const isSidebar = layout === "sidebar";
150
+ const slug = usePrototypeSlug();
199
151
  const [variantPreviewsReady, setVariantPreviewsReady] = useState(false);
200
152
 
201
153
  useEffect(() => {
@@ -204,6 +156,11 @@ export function PrototypeDesignBriefPanel<TVariant extends string>({
204
156
 
205
157
  const { copy: copyOverviewDefaultsPrompt, icon: copyOverviewDefaultsIcon } =
206
158
  useCopyToClipboard();
159
+ const {
160
+ copy: copyMoreVariantsPrompt,
161
+ icon: copyMoreVariantsIcon,
162
+ isCopied: isMoreVariantsCopied,
163
+ } = useCopyToClipboard();
207
164
  const { value: title, updateValue: updateTitle } = usePersistedLocalString(
208
165
  `${storageKeyPrefix}-overview-title`,
209
166
  brief.titleDefault,
@@ -220,11 +177,18 @@ export function PrototypeDesignBriefPanel<TVariant extends string>({
220
177
  `${storageKeyPrefix}-context-expanded`,
221
178
  context?.defaultExpanded ?? false,
222
179
  );
180
+ const { value: moreVariantsBrief, updateValue: updateMoreVariantsBrief } =
181
+ usePersistedLocalString(`${storageKeyPrefix}-more-variants-brief`, "");
182
+ const {
183
+ value: multipleVersions,
184
+ updateValue: updateMultipleVersions,
185
+ } = usePersistedLocalBoolean(
186
+ `${storageKeyPrefix}-more-variants-multiple-versions`,
187
+ true,
188
+ );
189
+ const canCopyMoreVariants = moreVariantsBrief.trim().length > 0 && Boolean(slug);
223
190
 
224
191
  const variantsTitle = variantsSection?.title ?? "Design variants";
225
- const variantsDescription =
226
- variantsSection?.description ??
227
- "Compare layout, copy, and CTAs side by side.";
228
192
 
229
193
  const effectiveTitle = title.trim() || brief.titleDefault;
230
194
  const effectiveDescription =
@@ -242,15 +206,9 @@ export function PrototypeDesignBriefPanel<TVariant extends string>({
242
206
  const sectionTitleClass = isSidebar
243
207
  ? SIDEBAR_GALLERY_SECTION_TITLE_CLASS
244
208
  : MODAL_GALLERY_SECTION_TITLE_CLASS;
245
- const sectionDescClass = isSidebar
246
- ? SIDEBAR_GALLERY_SECTION_DESC_CLASS
247
- : MODAL_GALLERY_SECTION_DESC_CLASS;
248
209
  const cardTitleClass = isSidebar
249
210
  ? SIDEBAR_GALLERY_CARD_TITLE_CLASS
250
211
  : MODAL_GALLERY_CARD_TITLE_CLASS;
251
- const cardBodyClass = isSidebar
252
- ? SIDEBAR_GALLERY_CARD_BODY_CLASS
253
- : MODAL_GALLERY_CARD_BODY_CLASS;
254
212
  const variantPreviewScale = isSidebar
255
213
  ? SIDEBAR_VARIANT_PREVIEW_SCALE
256
214
  : MODAL_VARIANT_PREVIEW_SCALE;
@@ -318,6 +276,188 @@ export function PrototypeDesignBriefPanel<TVariant extends string>({
318
276
  />
319
277
  ) : null;
320
278
 
279
+ const resolvedExplorationLabel = explorationLabel ?? brief.titleDefault;
280
+
281
+ const handleCopyMoreVariantsPrompt = () => {
282
+ if (!slug || !canCopyMoreVariants) return;
283
+
284
+ const origin =
285
+ typeof window !== "undefined" ? window.location.origin : undefined;
286
+
287
+ copyMoreVariantsPrompt(
288
+ buildMoreDesignExplorationVariantsCopyText({
289
+ slug,
290
+ explorationLabel: resolvedExplorationLabel,
291
+ explorationId,
292
+ options: explorationOptions,
293
+ brief: {
294
+ titleDefault: effectiveTitle,
295
+ descriptionDefault: effectiveDescription,
296
+ },
297
+ configFilePath: briefConfigFilePath,
298
+ origin,
299
+ briefText: moreVariantsBrief,
300
+ multipleVersions,
301
+ }),
302
+ );
303
+ };
304
+
305
+ const moreVariantsPromptSection = isSidebar ? (
306
+ <div className="flex flex-col gap-2">
307
+ <PrototypeComponent id={`${componentIdPrefix}.more-variants-prompt`}>
308
+ <PrototypeBriefTextarea
309
+ value={moreVariantsBrief}
310
+ onChange={(event) => updateMoreVariantsBrief(event.target.value)}
311
+ minHeightClass="min-h-[5rem]"
312
+ placeholder={
313
+ multipleVersions
314
+ ? "Describe more variations to explore…"
315
+ : "Describe one variation to explore…"
316
+ }
317
+ aria-label="More variations brief"
318
+ />
319
+ </PrototypeComponent>
320
+ <div className="flex items-center justify-between gap-4">
321
+ <PrototypeComponent id={`${componentIdPrefix}.more-variants-multiple-versions`}>
322
+ <label className="flex cursor-pointer items-center gap-2">
323
+ <Switch
324
+ checked={multipleVersions}
325
+ onCheckedChange={updateMultipleVersions}
326
+ aria-label="Multiple versions"
327
+ className="data-[state=checked]:bg-foreground"
328
+ />
329
+ <span className="text-sm text-[var(--text-secondary)]">Multiple versions</span>
330
+ </label>
331
+ </PrototypeComponent>
332
+ <PrototypeComponent id={`${componentIdPrefix}.more-variants-copy-prompt`}>
333
+ <Button
334
+ type="button"
335
+ variant="chrome"
336
+ size="sm"
337
+ className="h-8 gap-1.5"
338
+ disabled={!canCopyMoreVariants}
339
+ onClick={handleCopyMoreVariantsPrompt}
340
+ aria-label="Copy more variations prompt"
341
+ >
342
+ {isMoreVariantsCopied ? "Copied prompt" : "Copy prompt"}
343
+ {copyMoreVariantsIcon}
344
+ </Button>
345
+ </PrototypeComponent>
346
+ </div>
347
+ </div>
348
+ ) : null;
349
+
350
+ const explorationOptions = getDesignExplorationVariantOptions(options, baseline);
351
+ const resolvedBaseline = resolveDesignExplorationBaselineOption(baseline);
352
+
353
+ const renderVariantCard = (
354
+ option: DesignExplorationVariantOption<TVariant>,
355
+ {
356
+ isBaseline = false,
357
+ badgeSuffix,
358
+ }: { isBaseline?: boolean; badgeSuffix?: string } = {},
359
+ ) => {
360
+ const VariantPreview = renderers[option.value];
361
+ const isActive = option.value === variant;
362
+ const isDefaultOption =
363
+ !isBaseline &&
364
+ defaultVariantValue !== undefined &&
365
+ option.value === defaultVariantValue;
366
+ const optionIdSuffix = badgeSuffix ?? option.value;
367
+
368
+ return (
369
+ <PrototypeComponent
370
+ key={option.value}
371
+ id={`${componentIdPrefix}.overview-option.${optionIdSuffix}`}
372
+ >
373
+ <div className="flex flex-col gap-2">
374
+ <div className="flex items-start justify-between gap-2 px-0.5">
375
+ <div className="flex min-w-0 flex-1 items-center gap-2">
376
+ <p className={cn(cardTitleClass, "min-w-0 truncate")}>
377
+ {option.label}
378
+ </p>
379
+ {isBaseline ? (
380
+ <PrototypeComponent
381
+ id={`${componentIdPrefix}.overview-option.${optionIdSuffix}.baseline`}
382
+ className="tool-variant-original-badge shrink-0"
383
+ >
384
+ <span aria-label="Original state before exploration">
385
+ ORIGINAL
386
+ </span>
387
+ </PrototypeComponent>
388
+ ) : null}
389
+ </div>
390
+
391
+ <div className="flex shrink-0 items-center gap-3">
392
+ {isDefaultOption ? (
393
+ <PrototypeComponent
394
+ id={`${componentIdPrefix}.overview-option.${optionIdSuffix}.default`}
395
+ className="tool-variant-default-badge shrink-0"
396
+ >
397
+ <span aria-label="Current default variant">DEFAULT</span>
398
+ </PrototypeComponent>
399
+ ) : null}
400
+ {!isBaseline && !isDefaultOption ? (
401
+ <MakeDefaultButton
402
+ componentId={`${componentIdPrefix}.overview-option.${optionIdSuffix}.set-default`}
403
+ optionLabel={option.label}
404
+ optionValue={option.value}
405
+ configFilePath={briefConfigFilePath}
406
+ />
407
+ ) : null}
408
+ </div>
409
+ </div>
410
+
411
+ <div
412
+ role="button"
413
+ tabIndex={0}
414
+ aria-pressed={isActive}
415
+ aria-label={`Select ${option.label} variant`}
416
+ onClick={() => onVariantChange(option.value)}
417
+ onKeyDown={(event) => {
418
+ if (event.key === "Enter" || event.key === " ") {
419
+ event.preventDefault();
420
+ onVariantChange(option.value);
421
+ }
422
+ }}
423
+ className={cn(
424
+ "tool-variant-card",
425
+ isActive && "is-active",
426
+ )}
427
+ >
428
+ <div
429
+ style={{ zoom: variantPreviewScale }}
430
+ className="w-full"
431
+ data-prototype-screenshot
432
+ >
433
+ {variantPreviewsReady ? (
434
+ VariantPreview()
435
+ ) : (
436
+ <div
437
+ className="bg-bg-subtle min-h-32 w-full"
438
+ aria-hidden
439
+ />
440
+ )}
441
+ </div>
442
+ </div>
443
+
444
+ {option.rationale ? (
445
+ <p
446
+ className={cn(
447
+ "px-0.5 leading-relaxed",
448
+ isSidebar
449
+ ? "text-sm text-[var(--text-secondary)]"
450
+ : "text-muted-foreground text-xs",
451
+ )}
452
+ >
453
+ {option.rationale.good} {option.rationale.bad}
454
+ </p>
455
+ ) : null}
456
+ </div>
457
+ </PrototypeComponent>
458
+ );
459
+ };
460
+
321
461
  const variantsSectionContent = (
322
462
  <section
323
463
  className={cn(
@@ -333,125 +473,13 @@ export function PrototypeDesignBriefPanel<TVariant extends string>({
333
473
  )}
334
474
  >
335
475
  <p className={sectionTitleClass}>{variantsTitle}</p>
336
- {!isSidebar && <p className={sectionDescClass}>{variantsDescription}</p>}
337
476
  </div>
338
477
 
339
478
  <div className={cn("flex flex-col", isSidebar ? "gap-6" : "gap-10")}>
340
- {options.map((option) => {
341
- const optionRationale = rationale[option.value];
342
- const VariantPreview = renderers[option.value];
343
- const isActive = option.value === variant;
344
- const isDefaultOption =
345
- defaultVariantValue !== undefined &&
346
- option.value === defaultVariantValue;
347
-
348
- return (
349
- <PrototypeComponent
350
- key={option.value}
351
- id={`${componentIdPrefix}.overview-option.${option.value}`}
352
- >
353
- <div className="flex flex-col gap-2">
354
- <div className="flex items-start justify-between gap-2 px-0.5">
355
- <p className={cn(cardBodyClass, "min-w-0 flex-1")}>
356
- <span className={cardTitleClass}>{option.label}</span>
357
- {option.hint ? (
358
- <>
359
- {" · "}
360
- <VariantHint
361
- hint={option.hint}
362
- references={mobbin?.references ?? []}
363
- componentIdPrefix={componentIdPrefix}
364
- />
365
- </>
366
- ) : null}
367
- </p>
368
-
369
- <div className="flex shrink-0 items-center gap-3">
370
- {isDefaultOption ? (
371
- <PrototypeComponent
372
- id={`${componentIdPrefix}.overview-option.${option.value}.default`}
373
- >
374
- <Button
375
- type="button"
376
- variant="secondary"
377
- size="sm"
378
- className={cn(
379
- VARIANT_DEFAULT_CONTROL_CLASS,
380
- VARIANT_DEFAULT_ACTIVE_CLASS,
381
- "pointer-events-none disabled:opacity-100",
382
- )}
383
- disabled
384
- aria-label="Current default variant"
385
- >
386
- Default
387
- </Button>
388
- </PrototypeComponent>
389
- ) : onSetDefaultVariant ? (
390
- <PrototypeComponent
391
- id={`${componentIdPrefix}.overview-option.${option.value}.set-default`}
392
- >
393
- <Button
394
- type="button"
395
- variant="secondary"
396
- size="sm"
397
- className={VARIANT_DEFAULT_CONTROL_CLASS}
398
- onClick={(event) => {
399
- event.stopPropagation();
400
- onSetDefaultVariant(option.value);
401
- }}
402
- aria-label={`Set ${option.label} as default`}
403
- >
404
- Make default
405
- </Button>
406
- </PrototypeComponent>
407
- ) : null}
408
- </div>
409
- </div>
410
-
411
- <div
412
- role="button"
413
- tabIndex={0}
414
- aria-pressed={isActive}
415
- aria-label={`Select ${option.label} variant`}
416
- onClick={() => onVariantChange(option.value)}
417
- onKeyDown={(event) => {
418
- if (event.key === "Enter" || event.key === " ") {
419
- event.preventDefault();
420
- onVariantChange(option.value);
421
- }
422
- }}
423
- className={cn(
424
- "tool-variant-card",
425
- isActive && "is-active",
426
- )}
427
- >
428
- <div
429
- style={{ zoom: variantPreviewScale }}
430
- className="w-full"
431
- data-prototype-screenshot
432
- >
433
- {variantPreviewsReady ? (
434
- VariantPreview()
435
- ) : (
436
- <div
437
- className="bg-bg-subtle min-h-32 w-full"
438
- aria-hidden
439
- />
440
- )}
441
- </div>
442
- </div>
443
-
444
- <VariantRationaleSection
445
- componentIdPrefix={componentIdPrefix}
446
- optionValue={option.value}
447
- storageKeyPrefix={storageKeyPrefix}
448
- rationale={optionRationale}
449
- layout={layout}
450
- bodyClassName={cardBodyClass}
451
- />
452
- </div>
453
- </PrototypeComponent>
454
- );
479
+ {explorationOptions.map((option) => renderVariantCard(option))}
480
+ {renderVariantCard(resolvedBaseline, {
481
+ isBaseline: true,
482
+ badgeSuffix: "baseline",
455
483
  })}
456
484
  </div>
457
485
  </section>
@@ -469,6 +497,8 @@ export function PrototypeDesignBriefPanel<TVariant extends string>({
469
497
  >
470
498
  {isSidebar ? (
471
499
  <>
500
+ {moreVariantsPromptSection}
501
+ <hr className="border-border" />
472
502
  {variantsSectionContent}
473
503
  {mobbinSection ? (
474
504
  <>
@@ -276,12 +276,6 @@ $enter-duration: 150ms;
276
276
  background: rgba(255, 255, 255, 0.08) !important;
277
277
  color: rgba(255, 255, 255, 0.55) !important;
278
278
 
279
- > :first-child {
280
- border-radius: 0.375rem !important;
281
- background: rgba(255, 255, 255, 0.14) !important;
282
- box-shadow: none !important;
283
- }
284
-
285
279
  [role="tab"] {
286
280
  flex: 0 0 auto !important;
287
281
  height: 26px !important;
@@ -293,6 +287,10 @@ $enter-duration: 150ms;
293
287
  box-shadow: none !important;
294
288
  font-size: 0.8125rem !important;
295
289
  line-height: 1 !important;
290
+ transition:
291
+ background-color 0.15s ease,
292
+ color 0.15s ease,
293
+ box-shadow 0.15s ease;
296
294
  }
297
295
  }
298
296
 
@@ -305,14 +303,17 @@ $enter-duration: 150ms;
305
303
  color: rgba(255, 255, 255, 0.55) !important;
306
304
 
307
305
  @media (hover: hover) and (pointer: fine) {
308
- &:hover:not([data-active]) {
306
+ &:hover:not([data-state="active"]) {
309
307
  background: rgba(255, 255, 255, 0.06) !important;
310
308
  color: rgba(255, 255, 255, 0.75) !important;
311
309
  }
312
310
  }
313
311
 
314
- &[data-active] {
315
- color: rgba(255, 255, 255, 0.92) !important;
312
+ &[data-state="active"] {
313
+ background: rgba(0, 151, 254, 0.22) !important;
314
+ color: #fff !important;
315
+ box-shadow: inset 0 0 0 1px rgba(0, 151, 254, 0.55) !important;
316
+ font-weight: 600 !important;
316
317
  }
317
318
  }
318
319
 
@@ -1338,4 +1339,10 @@ $enter-duration: 150ms;
1338
1339
  .rationaleLabel {
1339
1340
  color: rgba(0, 0, 0, 0.92);
1340
1341
  }
1342
+
1343
+ .variantsTab[data-state="active"] {
1344
+ background: rgba(0, 120, 215, 0.12) !important;
1345
+ box-shadow: inset 0 0 0 1px rgba(0, 120, 215, 0.35) !important;
1346
+ color: rgba(0, 0, 0, 0.92) !important;
1347
+ }
1341
1348
  }