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.
- package/AGENTS.md +308 -29
- package/README.md +55 -13
- package/package.json +1 -1
- package/scripts/lib/host-config.example.sh +1 -1
- package/scripts/publish-package.sh +41 -0
- package/scripts/verify-prototype-preview-states.mjs +57 -0
- package/src/components/platform-ui/controls-panel-select.tsx +4 -3
- package/src/components/platform-ui/prototype-tool-dialog.tsx +117 -0
- package/src/components/platform-ui/sidebar.tsx +34 -16
- package/src/components/prototype-gallery-client.tsx +77 -37
- package/src/components/prototypes/design-exploration-variant-preview-shell.tsx +43 -0
- package/src/components/prototypes/prototype-brief-field.tsx +33 -0
- package/src/components/prototypes/prototype-change-log-panel.tsx +4 -0
- package/src/components/prototypes/prototype-comment-storage-empty-state.tsx +41 -0
- package/src/components/prototypes/prototype-create-exploration-modal.tsx +41 -64
- package/src/components/prototypes/prototype-create-prototype-modal.tsx +102 -0
- package/src/components/prototypes/prototype-create-state-modal.tsx +31 -70
- package/src/components/prototypes/prototype-default-state-map-page.tsx +4 -4
- package/src/components/prototypes/prototype-design-brief-modal.tsx +10 -10
- package/src/components/prototypes/prototype-design-brief-panel.tsx +261 -231
- package/src/components/prototypes/prototype-floating-pill.module.scss +16 -9
- package/src/components/prototypes/prototype-mobbin-gallery.tsx +18 -2
- package/src/components/prototypes/prototype-review-sidebar.tsx +11 -28
- package/src/components/prototypes/prototype-spec-panel-content.tsx +5 -5
- package/src/components/prototypes/prototype-spec-panel-empty-state.tsx +53 -0
- package/src/components/prototypes/prototype-state-canvas-overlay.module.scss +20 -47
- package/src/components/prototypes/prototype-state-canvas-overlay.tsx +4 -42
- package/src/components/prototypes/prototype-variant-explorer.tsx +32 -78
- package/src/components/prototypes/prototype-vercel-preview-modal.tsx +4 -3
- package/src/components/prototypes/state-map-annotation-tooltip.module.scss +7 -7
- package/src/components/shell/prototype-component-library-empty-state.tsx +59 -0
- package/src/components/shell/prototype-gallery-nav.tsx +124 -0
- package/src/components/shell/prototype-gallery-shell.tsx +45 -13
- package/src/components/shell/prototype-link-source-modal.tsx +100 -0
- package/src/components/ui/button.tsx +3 -1
- package/src/components/ui/dialog.tsx +43 -4
- package/src/config/create-prototype-component-library-page.tsx +74 -0
- package/src/config/create-prototype-gallery-page.tsx +26 -23
- package/src/config/create-prototype-host-app.tsx +3 -15
- package/src/config/create-prototype-site-layout.tsx +14 -1
- package/src/config/create-prototype-state-map-page.tsx +44 -0
- package/src/config/prototype-site-layout-client.tsx +20 -6
- package/src/config/with-prototype.ts +21 -0
- package/src/index.ts +54 -10
- package/src/lib/copy-text-with-attachments.ts +35 -0
- package/src/lib/format-source-directory-name.ts +12 -0
- package/src/lib/platform-ui/sidebar-interactive-cursor.ts +19 -0
- package/src/lib/pr-split/build-pr-split-plan-prompt.ts +81 -0
- package/src/lib/pr-split/pr-split-highlight.ts +3 -0
- package/src/lib/prototype-comments/core/annotation-target.ts +12 -3
- package/src/lib/prototype-comments/core/comment-capture-blocked.ts +24 -0
- package/src/lib/prototype-comments/core/screenshot.ts +68 -31
- package/src/lib/prototype-comments/core/storage.ts +3 -6
- package/src/lib/prototype-comments/core/types.ts +2 -0
- package/src/lib/prototype-comments/hooks/useLayoutShift.ts +9 -2
- package/src/lib/prototype-comments/react/CommentCaptureToolbar.tsx +45 -10
- package/src/lib/prototype-comments/react/CommentProvider.tsx +19 -0
- package/src/lib/prototype-comments/ui/CommentModeToggleButton.tsx +34 -0
- package/src/lib/prototype-comments/ui/CommentsGrid.tsx +8 -16
- package/src/lib/prototype-comments/ui/CommentsSidebar.tsx +5 -19
- package/src/lib/prototype-comments/ui/capture-styles.module.scss +5 -1
- package/src/lib/prototype-comments/ui/comments-grid-card.module.scss +0 -7
- package/src/lib/prototypes/build-design-exploration-renderers.ts +44 -0
- package/src/lib/prototypes/comment-storage-setup-prompt.ts +124 -0
- package/src/lib/prototypes/create-prototype-prompt.ts +96 -0
- package/src/lib/prototypes/create-prototype-registry.ts +5 -0
- package/src/lib/prototypes/design-exploration-types.ts +261 -10
- package/src/lib/prototypes/link-source-prompt.ts +93 -0
- package/src/lib/prototypes/populate-component-library-prompt.ts +64 -0
- package/src/lib/prototypes/prototype-config-types.ts +2 -0
- package/src/lib/prototypes/prototype-layout-classes.ts +19 -0
- package/src/lib/prototypes/prototype-preview-state-registry.ts +5 -2
- package/src/lib/prototypes/prototype-review-context.tsx +4 -4
- package/src/lib/prototypes/prototype-state-canvas-constants.ts +1 -1
- package/src/lib/prototypes/prototype-state-canvas-types.ts +0 -2
- package/src/lib/prototypes/prototype-storage-status.ts +31 -0
- package/src/lib/prototypes/sort-prototypes-by-creation-date.ts +43 -0
- package/src/lib/prototypes/use-prototype-tool-theme.tsx +1 -1
- package/src/lib/prototypes/use-toolbar-hover-panel.ts +12 -1
- package/src/lib/prototypes/variant-set-lucide-icon.tsx +24 -0
- package/src/lib/tool-portal.ts +24 -0
- package/src/lib/use-copy-to-clipboard.ts +7 -13
- package/src/server/create-host-api-route.ts +7 -16
- package/src/server/create-prototype-api-routes.ts +0 -115
- package/src/server/prototype-storage-status-route.ts +29 -0
- package/src/server.ts +0 -8
- package/src/styles/globals.css +359 -71
- package/src/lib/prototypes/design-exploration-default-storage.ts +0 -68
- package/src/lib/prototypes/design-exploration-default.ts +0 -51
- package/src/lib/prototypes/use-design-exploration-default.ts +0 -114
- package/src/server/redis/prototype-design-exploration-default.ts +0 -32
|
@@ -584,28 +584,20 @@ function CommentCard<TState>({
|
|
|
584
584
|
<div className={cardStyles.screenshotInner}>
|
|
585
585
|
<div
|
|
586
586
|
className={cardStyles.screenshot}
|
|
587
|
-
style={
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
}
|
|
587
|
+
style={{
|
|
588
|
+
aspectRatio: aspectRatio ?? "16 / 9",
|
|
589
|
+
...(isSidebarLayout ? { maxHeight: 220 } : {}),
|
|
590
|
+
}}
|
|
592
591
|
>
|
|
593
592
|
{annotation.screenshot ? (
|
|
594
593
|
<>
|
|
595
594
|
<img
|
|
596
595
|
src={annotation.screenshot}
|
|
597
596
|
alt={annotation.comment}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
isSidebarLayout
|
|
603
|
-
? { objectPosition: screenshotObjectPosition }
|
|
604
|
-
: {
|
|
605
|
-
...s.screenshotImg,
|
|
606
|
-
objectPosition: screenshotObjectPosition,
|
|
607
|
-
}
|
|
608
|
-
}
|
|
597
|
+
style={{
|
|
598
|
+
...s.screenshotImg,
|
|
599
|
+
objectPosition: screenshotObjectPosition,
|
|
600
|
+
}}
|
|
609
601
|
/>
|
|
610
602
|
{viewportBox && viewport && (
|
|
611
603
|
<div
|
|
@@ -3,14 +3,9 @@
|
|
|
3
3
|
import { countUnresolvedAnnotations } from "../core/annotation-status";
|
|
4
4
|
import { useCommentStore } from "../react/CommentProvider";
|
|
5
5
|
import { CommentHeaderCount } from "./CommentHeaderCount";
|
|
6
|
+
import { CommentModeToggleButton } from "./CommentModeToggleButton";
|
|
6
7
|
import { CommentsGrid } from "./CommentsGrid";
|
|
7
|
-
import {
|
|
8
|
-
SIDEBAR_ICON_BUTTON_CLASS,
|
|
9
|
-
Sidebar,
|
|
10
|
-
} from "@prototype/components/platform-ui/sidebar";
|
|
11
|
-
import { IconButton } from "@prototype/components/platform-ui/icon-button";
|
|
12
|
-
import { cn } from "@prototype/lib/utils";
|
|
13
|
-
import { MessageSquarePlus } from "lucide-react";
|
|
8
|
+
import { Sidebar } from "@prototype/components/platform-ui/sidebar";
|
|
14
9
|
|
|
15
10
|
type CommentsSidebarProps = {
|
|
16
11
|
open: boolean;
|
|
@@ -47,19 +42,10 @@ export function CommentsSidebar({
|
|
|
47
42
|
widthCssVar="--comments-sidebar-width"
|
|
48
43
|
headerActions={
|
|
49
44
|
onToggleCommentMode ? (
|
|
50
|
-
<
|
|
51
|
-
|
|
45
|
+
<CommentModeToggleButton
|
|
46
|
+
isActive={isCommentModeActive}
|
|
52
47
|
onClick={onToggleCommentMode}
|
|
53
|
-
|
|
54
|
-
aria-label={isCommentModeActive ? "Exit comment mode" : "Add comment"}
|
|
55
|
-
aria-pressed={isCommentModeActive}
|
|
56
|
-
className={cn(
|
|
57
|
-
SIDEBAR_ICON_BUTTON_CLASS,
|
|
58
|
-
isCommentModeActive && "bg-accent text-accent-foreground",
|
|
59
|
-
)}
|
|
60
|
-
>
|
|
61
|
-
<MessageSquarePlus size={16} strokeWidth={2} />
|
|
62
|
-
</IconButton>
|
|
48
|
+
/>
|
|
63
49
|
) : null
|
|
64
50
|
}
|
|
65
51
|
>
|
|
@@ -870,9 +870,13 @@
|
|
|
870
870
|
}
|
|
871
871
|
|
|
872
872
|
:global(html[data-comments-sidebar-open]) .markersLayer,
|
|
873
|
+
:global(html[data-prototype-review-sidebar-open]) .markersLayer,
|
|
873
874
|
:global(html[data-comments-sidebar-open]) .fixedMarkersLayer,
|
|
875
|
+
:global(html[data-prototype-review-sidebar-open]) .fixedMarkersLayer,
|
|
874
876
|
:global(html[data-comments-sidebar-open]) .overlay,
|
|
875
|
-
:global(html[data-
|
|
877
|
+
:global(html[data-prototype-review-sidebar-open]) .overlay,
|
|
878
|
+
:global(html[data-comments-sidebar-open]) .targetHighlightLayer,
|
|
879
|
+
:global(html[data-prototype-review-sidebar-open]) .targetHighlightLayer {
|
|
876
880
|
right: var(--comments-sidebar-width, 400px);
|
|
877
881
|
}
|
|
878
882
|
|
|
@@ -42,13 +42,6 @@
|
|
|
42
42
|
transition: border-color 0.2s cubic-bezier(0.215, 0.61, 0.355, 1);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
.screenshotImgSidebar {
|
|
46
|
-
display: block;
|
|
47
|
-
width: 100%;
|
|
48
|
-
height: auto;
|
|
49
|
-
max-height: 220px;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
45
|
.cardCondensed .screenshot {
|
|
53
46
|
border-bottom-color: transparent;
|
|
54
47
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
DesignExplorationBaselineOption,
|
|
5
|
+
DesignExplorationVariantOption,
|
|
6
|
+
} from "./design-exploration-types";
|
|
7
|
+
import { getDesignExplorationDisplayOptions } from "./design-exploration-types";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Builds a renderer entry for every design exploration option. Prefer this over
|
|
11
|
+
* hand-rolled `Object.fromEntries` maps so each variant gets a preview and new
|
|
12
|
+
* options cannot silently return `null`.
|
|
13
|
+
*/
|
|
14
|
+
export function buildDesignExplorationRenderers<TVariant extends string>(
|
|
15
|
+
options: readonly DesignExplorationVariantOption<TVariant>[],
|
|
16
|
+
renderVariant: (variant: TVariant) => ReactNode,
|
|
17
|
+
baseline?: DesignExplorationBaselineOption<TVariant>,
|
|
18
|
+
): Record<TVariant, () => ReactNode> {
|
|
19
|
+
const displayOptions = baseline
|
|
20
|
+
? getDesignExplorationDisplayOptions([...options], baseline)
|
|
21
|
+
: options;
|
|
22
|
+
const renderers = {} as Record<TVariant, () => ReactNode>;
|
|
23
|
+
|
|
24
|
+
for (const option of displayOptions) {
|
|
25
|
+
const variant = option.value as TVariant;
|
|
26
|
+
renderers[variant] = () => {
|
|
27
|
+
const node = renderVariant(variant);
|
|
28
|
+
|
|
29
|
+
if (process.env.NODE_ENV !== "production" && isEmptyRendererOutput(node)) {
|
|
30
|
+
console.warn(
|
|
31
|
+
`[prototype] Design exploration renderer for "${variant}" returned no preview content.`,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return node;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return renderers;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isEmptyRendererOutput(node: ReactNode): boolean {
|
|
43
|
+
return node == null || node === false;
|
|
44
|
+
}
|
|
@@ -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 {
|