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
@@ -0,0 +1,100 @@
1
+ "use client";
2
+
3
+ import { PrototypeToolDialog } from "@prototype/components/platform-ui/prototype-tool-dialog";
4
+ import { Button } from "@prototype/components/ui/button";
5
+ import { Input } from "@prototype/components/ui/input";
6
+ import { Label } from "@prototype/components/ui/label";
7
+ import { PROTOTYPE_BRIEF_FIELD_BORDER_CLASS } from "@prototype/components/prototypes/prototype-brief-field";
8
+ import { buildLinkSourceCopyText } from "@prototype/lib/prototypes/link-source-prompt";
9
+ import useCopyToClipboard from "@prototype/lib/use-copy-to-clipboard";
10
+ import { useEffect, useId, useState } from "react";
11
+ import { toast } from "sonner";
12
+
13
+ export type PrototypeLinkSourceModalProps = {
14
+ open: boolean;
15
+ onOpenChange: (open: boolean) => void;
16
+ currentSourcePath?: string;
17
+ };
18
+
19
+ export function PrototypeLinkSourceModal({
20
+ open,
21
+ onOpenChange,
22
+ currentSourcePath,
23
+ }: PrototypeLinkSourceModalProps) {
24
+ const pathFieldId = useId();
25
+ const [sourcePath, setSourcePath] = useState("");
26
+ const [isCopying, setIsCopying] = useState(false);
27
+ const { copy, icon, isCopied } = useCopyToClipboard();
28
+ const canCopy = sourcePath.trim().length > 0;
29
+ const isUpdate = Boolean(currentSourcePath?.trim());
30
+
31
+ useEffect(() => {
32
+ if (!open) {
33
+ setSourcePath("");
34
+ return;
35
+ }
36
+
37
+ setSourcePath(currentSourcePath?.trim() ?? "");
38
+ }, [open, currentSourcePath]);
39
+
40
+ const handleCopy = async () => {
41
+ if (!canCopy || isCopying) return;
42
+
43
+ setIsCopying(true);
44
+
45
+ try {
46
+ await copy(
47
+ buildLinkSourceCopyText({
48
+ sourcePath,
49
+ currentSourcePath,
50
+ }),
51
+ );
52
+ } catch {
53
+ toast.error("Copy error", {
54
+ description: "Failed to copy link source prompt",
55
+ });
56
+ } finally {
57
+ setIsCopying(false);
58
+ }
59
+ };
60
+
61
+ return (
62
+ <PrototypeToolDialog
63
+ open={open}
64
+ onOpenChange={onOpenChange}
65
+ title={isUpdate ? "Change source app" : "Link source app"}
66
+ description="Set the codebase for which you're making prototypes."
67
+ bodyClassName="gap-4"
68
+ footerClassName="justify-end"
69
+ footer={
70
+ <Button
71
+ type="button"
72
+ variant="chrome"
73
+ size="sm"
74
+ className="h-8 gap-1.5"
75
+ disabled={!canCopy || isCopying}
76
+ onClick={() => {
77
+ void handleCopy();
78
+ }}
79
+ aria-label="Copy link source prompt"
80
+ >
81
+ {isCopied ? "Copied prompt" : isCopying ? "Copying…" : "Copy prompt"}
82
+ {icon}
83
+ </Button>
84
+ }
85
+ >
86
+ <div className="flex flex-col gap-1.5">
87
+ <Label htmlFor={pathFieldId} className="text-foreground">
88
+ Source path, description of folder, or folder name
89
+ </Label>
90
+ <Input
91
+ id={pathFieldId}
92
+ value={sourcePath}
93
+ onChange={(event) => setSourcePath(event.target.value)}
94
+ placeholder="ex. ../cal.diy, cal.diy, or sibling folder to this host app"
95
+ className={PROTOTYPE_BRIEF_FIELD_BORDER_CLASS}
96
+ />
97
+ </div>
98
+ </PrototypeToolDialog>
99
+ );
100
+ }
@@ -7,7 +7,7 @@ import * as React from "react";
7
7
  import { cn } from "@prototype/lib/utils";
8
8
 
9
9
  const buttonVariants = cva(
10
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
10
+ "inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
11
11
  {
12
12
  variants: {
13
13
  variant: {
@@ -18,6 +18,8 @@ const buttonVariants = cva(
18
18
  "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
19
19
  secondary:
20
20
  "bg-secondary text-secondary-foreground hover:bg-secondary/80",
21
+ chrome:
22
+ "border-0 bg-[var(--tool-chrome-button-bg)] text-[var(--tool-chrome-button-text)] shadow-none hover:bg-[var(--tool-chrome-button-bg-hover)] hover:text-[var(--tool-chrome-button-text-hover)]",
21
23
  ghost: "hover:bg-accent hover:text-accent-foreground",
22
24
  link: "text-primary underline-offset-4 hover:underline",
23
25
  },
@@ -4,7 +4,11 @@ import * as DialogPrimitive from "@radix-ui/react-dialog";
4
4
  import { X } from "lucide-react";
5
5
  import * as React from "react";
6
6
 
7
- import { getPrototypePortalContainer } from "@prototype/lib/tool-portal";
7
+ import { isCommentCaptureBlockedTarget } from "@prototype/lib/prototype-comments/core/comment-capture-blocked";
8
+ import {
9
+ getPrototypeDialogPortalContainer,
10
+ getPrototypeToolDialogPortalContainer,
11
+ } from "@prototype/lib/tool-portal";
8
12
  import { cn } from "@prototype/lib/utils";
9
13
 
10
14
  import styles from "./dialog.module.scss";
@@ -14,6 +18,19 @@ const DialogTrigger = DialogPrimitive.Trigger;
14
18
  const DialogClose = DialogPrimitive.Close;
15
19
  const DialogPortal = DialogPrimitive.Portal;
16
20
 
21
+ function isPrototypeToolOutsideEventTarget(target: EventTarget | null): boolean {
22
+ return target instanceof HTMLElement && isCommentCaptureBlockedTarget(target);
23
+ }
24
+
25
+ function ignorePrototypeToolOutsideEvent(event: {
26
+ preventDefault: () => void;
27
+ target: EventTarget | null;
28
+ }): void {
29
+ if (isPrototypeToolOutsideEventTarget(event.target)) {
30
+ event.preventDefault();
31
+ }
32
+ }
33
+
17
34
  const DialogOverlay = React.forwardRef<
18
35
  React.ComponentRef<typeof DialogPrimitive.Overlay>,
19
36
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
@@ -30,14 +47,24 @@ const DialogOverlay = React.forwardRef<
30
47
  ));
31
48
  DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
32
49
 
50
+ export type DialogPortalScope = "viewport" | "tool";
51
+
33
52
  const DialogContent = React.forwardRef<
34
53
  React.ComponentRef<typeof DialogPrimitive.Content>,
35
54
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
36
55
  showCloseButton?: boolean;
56
+ overlayClassName?: string;
57
+ portalScope?: DialogPortalScope;
37
58
  }
38
- >(({ className, children, showCloseButton = true, ...props }, ref) => (
39
- <DialogPortal container={getPrototypePortalContainer()}>
40
- <DialogOverlay />
59
+ >(({ className, children, showCloseButton = true, overlayClassName, portalScope = "viewport", onPointerDownOutside, onInteractOutside, onFocusOutside, ...props }, ref) => (
60
+ <DialogPortal
61
+ container={
62
+ portalScope === "tool"
63
+ ? getPrototypeToolDialogPortalContainer()
64
+ : getPrototypeDialogPortalContainer()
65
+ }
66
+ >
67
+ <DialogOverlay className={overlayClassName} />
41
68
  <DialogPrimitive.Content
42
69
  ref={ref}
43
70
  className={cn(
@@ -45,6 +72,18 @@ const DialogContent = React.forwardRef<
45
72
  "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg gap-4 border bg-background p-6 shadow-lg sm:rounded-lg",
46
73
  className,
47
74
  )}
75
+ onPointerDownOutside={(event) => {
76
+ ignorePrototypeToolOutsideEvent(event);
77
+ onPointerDownOutside?.(event);
78
+ }}
79
+ onInteractOutside={(event) => {
80
+ ignorePrototypeToolOutsideEvent(event);
81
+ onInteractOutside?.(event);
82
+ }}
83
+ onFocusOutside={(event) => {
84
+ ignorePrototypeToolOutsideEvent(event);
85
+ onFocusOutside?.(event);
86
+ }}
48
87
  {...props}
49
88
  >
50
89
  {children}
@@ -0,0 +1,74 @@
1
+ import {
2
+ PrototypeGalleryHeader,
3
+ PrototypeGalleryPageLayout,
4
+ } from "@prototype/components/shell/prototype-gallery-shell";
5
+ import { PrototypeComponentLibraryEmptyState } from "@prototype/components/shell/prototype-component-library-empty-state";
6
+ import { formatSourceDirectoryName } from "@prototype/lib/format-source-directory-name";
7
+ import { PROTOTYPE_SOURCE_SURFACE_ATTR } from "@prototype/lib/tool-portal";
8
+ import type { ReactNode } from "react";
9
+
10
+ type PrototypeComponentLibraryPageOptions = {
11
+ title?: string;
12
+ description?: string;
13
+ eyebrow?: string;
14
+ children?: ReactNode;
15
+ sourcePath?: string;
16
+ syncConfigPath?: string;
17
+ componentLibraryPagePath?: string;
18
+ };
19
+
20
+ export function createPrototypeComponentLibraryPage(
21
+ options: PrototypeComponentLibraryPageOptions = {},
22
+ ) {
23
+ const {
24
+ title = "Component Library",
25
+ description = "Shared UI primitives and building blocks used across prototypes.",
26
+ eyebrow,
27
+ children,
28
+ sourcePath = process.env.SOURCE_PATH,
29
+ syncConfigPath,
30
+ componentLibraryPagePath,
31
+ } = options;
32
+
33
+ const sourceDirectoryName = formatSourceDirectoryName(sourcePath);
34
+ const isEmpty = !children;
35
+
36
+ function ComponentLibraryPage() {
37
+ return (
38
+ <PrototypeGalleryPageLayout
39
+ header={
40
+ <PrototypeGalleryHeader
41
+ eyebrow={eyebrow}
42
+ title={title}
43
+ description={description}
44
+ className="mb-0"
45
+ />
46
+ }
47
+ >
48
+ {isEmpty ? (
49
+ <div className="px-10 pt-8 pb-16 lg:px-16">
50
+ <PrototypeComponentLibraryEmptyState
51
+ sourcePath={sourceDirectoryName ?? sourcePath}
52
+ syncConfigPath={syncConfigPath}
53
+ componentLibraryPagePath={componentLibraryPagePath}
54
+ />
55
+ </div>
56
+ ) : (
57
+ // Isolate synced host design-system components from the tool chrome
58
+ // theme scoped to [data-prototype-root]. This surface pins neutral
59
+ // product tokens (see packages/prototype/src/styles/globals.css), so
60
+ // previews never inherit tool-chrome --primary/--font-sans. Hosts layer
61
+ // their brand token values by scoping to [data-prototype-source-surface].
62
+ <div
63
+ className="px-10 pt-8 pb-16 lg:px-16"
64
+ {...{ [PROTOTYPE_SOURCE_SURFACE_ATTR]: "" }}
65
+ >
66
+ {children}
67
+ </div>
68
+ )}
69
+ </PrototypeGalleryPageLayout>
70
+ );
71
+ }
72
+
73
+ return ComponentLibraryPage;
74
+ }
@@ -1,11 +1,15 @@
1
1
  import "server-only";
2
2
 
3
3
  import { PrototypeGalleryClient } from "@prototype/components/prototype-gallery-client";
4
- import { PrototypeGalleryHeader } from "@prototype/components/shell/prototype-gallery-shell";
4
+ import {
5
+ PrototypeGalleryHeader,
6
+ PrototypeGalleryPageLayout,
7
+ } from "@prototype/components/shell/prototype-gallery-shell";
5
8
  import { createPrototypeRegistry } from "@prototype/lib/prototypes/create-prototype-registry";
6
9
  import type { PrototypeConfig } from "@prototype/lib/prototypes/prototype-config-types";
7
10
 
8
11
  import { readScreenshotManifest } from "../lib/prototypes/screenshot-manifest";
12
+ import { sortPrototypesByCreationDate } from "../lib/prototypes/sort-prototypes-by-creation-date";
9
13
 
10
14
  type PrototypeGalleryPageOptions = {
11
15
  title?: string;
@@ -25,24 +29,17 @@ export function createPrototypeGalleryPage(
25
29
  } = options;
26
30
 
27
31
  async function PrototypeGallery() {
28
- const prototypes = registry.getAllPrototypes();
32
+ const [prototypes, screenshotManifest] = await Promise.all([
33
+ Promise.resolve(registry.getAllPrototypes()),
34
+ readScreenshotManifest(),
35
+ ]);
36
+ const sortedPrototypes = await sortPrototypesByCreationDate(prototypes);
29
37
  const screenshotVersions =
30
- process.env.NODE_ENV === "development"
31
- ? await readScreenshotManifest()
32
- : {};
33
-
34
- if (prototypes.length === 0) {
35
- return (
36
- <p className="text-muted-foreground py-12 text-sm">
37
- No prototypes yet. Add one in{" "}
38
- <code className="text-foreground">prototype.config.ts</code>.
39
- </p>
40
- );
41
- }
38
+ process.env.NODE_ENV === "development" ? screenshotManifest : {};
42
39
 
43
40
  return (
44
41
  <PrototypeGalleryClient
45
- prototypes={prototypes}
42
+ prototypes={sortedPrototypes}
46
43
  screenshotVersions={screenshotVersions}
47
44
  />
48
45
  );
@@ -50,14 +47,20 @@ export function createPrototypeGalleryPage(
50
47
 
51
48
  function HomePage() {
52
49
  return (
53
- <div className="px-10 lg:px-16">
54
- <PrototypeGalleryHeader
55
- eyebrow={eyebrow}
56
- title={title}
57
- description={description}
58
- />
59
- <PrototypeGallery />
60
- </div>
50
+ <PrototypeGalleryPageLayout
51
+ header={
52
+ <PrototypeGalleryHeader
53
+ eyebrow={eyebrow}
54
+ title={title}
55
+ description={description}
56
+ className="mb-0"
57
+ />
58
+ }
59
+ >
60
+ <div className="px-10 pt-8 pb-16 lg:px-16">
61
+ <PrototypeGallery />
62
+ </div>
63
+ </PrototypeGalleryPageLayout>
61
64
  );
62
65
  }
63
66
 
@@ -4,7 +4,6 @@ import { notFound } from "next/navigation";
4
4
  import { Suspense } from "react";
5
5
 
6
6
  import { PrototypeShell } from "@prototype/components/shell/prototype-shell";
7
- import { PrototypeDefaultStateMapPage } from "@prototype/components/prototypes/prototype-default-state-map-page";
8
7
  import { createPrototypeRegistry } from "@prototype/lib/prototypes/create-prototype-registry";
9
8
  import type {
10
9
  PrototypeConfig,
@@ -12,6 +11,7 @@ import type {
12
11
  } from "@prototype/lib/prototypes/prototype-config-types";
13
12
 
14
13
  import { createPrototypePage } from "./create-prototype-page";
14
+ import { createPrototypeStateMapPage } from "./create-prototype-state-map-page";
15
15
 
16
16
  function matchExtraRoute(
17
17
  path: string[],
@@ -51,6 +51,7 @@ function findExtraRoute(
51
51
  export function createPrototypeCatchAllPage(config: PrototypeConfig) {
52
52
  const registry = createPrototypeRegistry(config);
53
53
  const prototypePage = createPrototypePage(config);
54
+ const stateMapPage = createPrototypeStateMapPage(config);
54
55
 
55
56
  function generateStaticParams() {
56
57
  const prototypePaths = prototypePage.generateStaticParams().map(({ slug }) => ({
@@ -98,20 +99,7 @@ export function createPrototypeCatchAllPage(config: PrototypeConfig) {
98
99
 
99
100
  if (path.length === 3 && path[0] === "prototypes" && path[2] === "states") {
100
101
  const slug = path[1]!;
101
- const prototype = registry.getPrototype(slug);
102
- const componentRegistry = registry.getPrototypeComponentRegistryForSlug(slug);
103
-
104
- if (!prototype) {
105
- notFound();
106
- }
107
-
108
- return (
109
- <Suspense fallback={null}>
110
- <PrototypeShell slug={slug} componentRegistry={componentRegistry}>
111
- <PrototypeDefaultStateMapPage slug={slug} />
112
- </PrototypeShell>
113
- </Suspense>
114
- );
102
+ return stateMapPage.default({ params: Promise.resolve({ slug }) });
115
103
  }
116
104
 
117
105
  notFound();
@@ -2,15 +2,22 @@ import type { ComponentType, ReactNode } from "react";
2
2
 
3
3
  import { PrototypeGalleryShell } from "@prototype/components/shell/prototype-gallery-shell";
4
4
 
5
+ import { formatSourceDirectoryName } from "@prototype/lib/format-source-directory-name";
6
+
5
7
  import { PrototypeSiteLayoutClient } from "./prototype-site-layout-client";
6
8
 
7
9
  type PrototypeSiteLayoutOptions = {
8
10
  toaster?: ReactNode;
11
+ sourceDirectoryName?: string;
9
12
  };
10
13
 
11
14
  export function createPrototypeSiteLayout(
12
15
  options: PrototypeSiteLayoutOptions = {},
13
16
  ) {
17
+ const sourcePath = process.env.SOURCE_PATH;
18
+ const sourceDirectoryName =
19
+ options.sourceDirectoryName ?? formatSourceDirectoryName(sourcePath);
20
+
14
21
  return function PrototypeSiteLayout({
15
22
  children,
16
23
  }: Readonly<{
@@ -18,7 +25,13 @@ export function createPrototypeSiteLayout(
18
25
  }>) {
19
26
  return (
20
27
  <PrototypeSiteLayoutClient
21
- galleryShell={PrototypeGalleryShell as ComponentType<{ children: ReactNode }>}
28
+ galleryShell={PrototypeGalleryShell as ComponentType<{
29
+ children: ReactNode;
30
+ sourceDirectoryName?: string;
31
+ sourcePath?: string;
32
+ }>}
33
+ sourceDirectoryName={sourceDirectoryName}
34
+ sourcePath={sourcePath}
22
35
  toaster={options.toaster}
23
36
  >
24
37
  {children}
@@ -0,0 +1,44 @@
1
+ import { notFound } from "next/navigation";
2
+ import { Suspense } from "react";
3
+
4
+ import { PrototypeDefaultStateMapPage } from "@prototype/components/prototypes/prototype-default-state-map-page";
5
+ import { PrototypeShell } from "@prototype/components/shell/prototype-shell";
6
+ import { createPrototypeRegistry } from "@prototype/lib/prototypes/create-prototype-registry";
7
+ import type { PrototypeConfig } from "@prototype/lib/prototypes/prototype-config-types";
8
+
9
+ type PrototypeStateMapPageProps = {
10
+ params: Promise<{ slug: string }>;
11
+ };
12
+
13
+ export function createPrototypeStateMapPage(config: PrototypeConfig) {
14
+ const registry = createPrototypeRegistry(config);
15
+
16
+ function generateStaticParams() {
17
+ return registry.getAllPrototypes().map(({ slug }) => ({ slug }));
18
+ }
19
+
20
+ async function PrototypeStateMapPage({ params }: PrototypeStateMapPageProps) {
21
+ const { slug } = await params;
22
+ const prototype = registry.getPrototype(slug);
23
+ const StateMapComponent = registry.getPrototypeStateMapComponent(slug);
24
+ const componentRegistry = registry.getPrototypeComponentRegistryForSlug(slug);
25
+
26
+ if (!prototype) {
27
+ notFound();
28
+ }
29
+
30
+ return (
31
+ <Suspense fallback={null}>
32
+ <PrototypeShell slug={slug} componentRegistry={componentRegistry}>
33
+ {StateMapComponent ? (
34
+ <StateMapComponent />
35
+ ) : (
36
+ <PrototypeDefaultStateMapPage slug={slug} />
37
+ )}
38
+ </PrototypeShell>
39
+ </Suspense>
40
+ );
41
+ }
42
+
43
+ return { default: PrototypeStateMapPage, generateStaticParams };
44
+ }
@@ -5,24 +5,38 @@ import type { ComponentType, ReactNode } from "react";
5
5
 
6
6
  type PrototypeSiteLayoutClientProps = {
7
7
  children: ReactNode;
8
- galleryShell: ComponentType<{ children: ReactNode }>;
8
+ galleryShell: ComponentType<{
9
+ children: ReactNode;
10
+ sourceDirectoryName?: string;
11
+ sourcePath?: string;
12
+ }>;
13
+ sourceDirectoryName?: string;
14
+ sourcePath?: string;
9
15
  toaster: ReactNode;
10
16
  };
11
17
 
12
18
  export function PrototypeSiteLayoutClient({
13
19
  children,
14
20
  galleryShell: GalleryShell,
21
+ sourceDirectoryName,
22
+ sourcePath,
15
23
  toaster,
16
24
  }: PrototypeSiteLayoutClientProps) {
17
25
  const pathname = usePathname();
18
- const isGalleryHome = pathname === "/";
26
+ const isGalleryRoute =
27
+ pathname === "/" || pathname.startsWith("/component-library");
19
28
 
20
- if (isGalleryHome) {
29
+ if (isGalleryRoute) {
21
30
  return (
22
- <>
23
- <GalleryShell>{children}</GalleryShell>
31
+ <div className="bg-[var(--bg-main)] flex h-svh min-h-svh w-full flex-col overflow-hidden">
32
+ <GalleryShell
33
+ sourceDirectoryName={sourceDirectoryName}
34
+ sourcePath={sourcePath}
35
+ >
36
+ {children}
37
+ </GalleryShell>
24
38
  {toaster}
25
- </>
39
+ </div>
26
40
  );
27
41
  }
28
42
 
@@ -12,9 +12,30 @@ export function withPrototype(
12
12
 
13
13
  return {
14
14
  ...nextConfig,
15
+ // Next.js 16 blocks cross-origin dev asset/HMR requests by default.
16
+ // Include ngrok (mobile testing) and common local host aliases (Glass browser, 127.0.0.1).
17
+ allowedDevOrigins: [
18
+ ...(nextConfig.allowedDevOrigins ?? []),
19
+ "127.0.0.1",
20
+ "[::1]",
21
+ "*.ngrok-free.app",
22
+ "*.ngrok-free.dev",
23
+ "*.ngrok.app",
24
+ "*.ngrok.io",
25
+ "*.ngrok.dev",
26
+ ],
15
27
  transpilePackages: [
16
28
  ...(nextConfig.transpilePackages ?? []),
17
29
  "proto-plugin",
18
30
  ],
31
+ images: {
32
+ ...nextConfig.images,
33
+ localPatterns: [
34
+ {
35
+ pathname: "/prototypes/screenshots/**",
36
+ },
37
+ ...(nextConfig.images?.localPatterns ?? []),
38
+ ],
39
+ },
19
40
  };
20
41
  }
package/src/index.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  export { definePrototypeConfig } from "./config/define-prototype-config";
2
+ export { createPrototypeGalleryPage } from "./config/create-prototype-gallery-page";
3
+ export { createPrototypeComponentLibraryPage } from "./config/create-prototype-component-library-page";
2
4
  export { createPrototypePage } from "./config/create-prototype-page";
5
+ export { createPrototypeStateMapPage } from "./config/create-prototype-state-map-page";
3
6
  export { createPrototypeSiteLayout } from "./config/create-prototype-site-layout";
4
7
  export { withPrototype } from "./config/with-prototype";
5
8
 
@@ -44,6 +47,12 @@ export type { PrototypeVercelPreviewModalProps } from "./components/prototypes/p
44
47
  export { PrototypeTweaks } from "./components/prototypes/prototype-tweaks";
45
48
  export type { PrototypeTweakConfig } from "./components/prototypes/prototype-tweaks";
46
49
 
50
+ export {
51
+ PROTOTYPE_PAGE_CLASS,
52
+ PROTOTYPE_SCROLL_CONTAINER_CLASS,
53
+ PROTOTYPE_VIEW_SHELL_CLASS,
54
+ } from "./lib/prototypes/prototype-layout-classes";
55
+
47
56
  export { usePrototypeComments } from "./lib/prototypes/use-prototype-comments";
48
57
 
49
58
  export {
@@ -51,19 +60,51 @@ export {
51
60
  readPersistedPrototypeLiveState,
52
61
  } from "./lib/prototypes/use-persisted-local-state";
53
62
 
54
- export type { DesignExplorationConfig } from "./lib/prototypes/design-exploration-types";
63
+ export type {
64
+ DesignExplorationBaselineOption,
65
+ DesignExplorationConfig,
66
+ } from "./lib/prototypes/design-exploration-types";
55
67
 
56
68
  export {
57
69
  buildCreateDesignExplorationCopyText,
58
70
  buildCreateDesignExplorationPrompt,
71
+ buildMoreDesignExplorationVariantsCopyText,
72
+ buildMoreDesignExplorationVariantsPrompt,
59
73
  buildResetBriefDefaultsPrompt,
74
+ DESIGN_EXPLORATION_BASELINE_LABEL,
60
75
  getAdjacentDesignExplorationVariant,
76
+ getDesignExplorationDisplayOptions,
77
+ getDesignExplorationVariantOptions,
78
+ resolveDesignExplorationBaselineOption,
61
79
  } from "./lib/prototypes/design-exploration-types";
62
80
 
81
+ export { buildDesignExplorationRenderers } from "./lib/prototypes/build-design-exploration-renderers";
82
+
83
+ export {
84
+ DesignExplorationVariantPreviewShell,
85
+ type DesignExplorationVariantPreviewLayout,
86
+ type DesignExplorationVariantPreviewShellProps,
87
+ } from "./components/prototypes/design-exploration-variant-preview-shell";
88
+
63
89
  export {
64
90
  buildCreatePreviewStateCopyText,
65
91
  buildCreatePreviewStatePrompt,
66
92
  } from "./lib/prototypes/prototype-preview-state-types";
93
+
94
+ export {
95
+ buildCreatePrototypeCopyText,
96
+ buildCreatePrototypePrompt,
97
+ suggestPrototypeSlug,
98
+ } from "./lib/prototypes/create-prototype-prompt";
99
+
100
+ export {
101
+ buildPopulateComponentLibraryCopyText,
102
+ buildPopulateComponentLibraryPrompt,
103
+ } from "./lib/prototypes/populate-component-library-prompt";
104
+ export {
105
+ buildLinkSourceCopyText,
106
+ buildLinkSourcePrompt,
107
+ } from "./lib/prototypes/link-source-prompt";
67
108
  export type { PrototypePreviewState } from "./lib/prototypes/prototype-preview-state-types";
68
109
 
69
110
  export {
@@ -113,6 +154,7 @@ export {
113
154
  PrototypeGalleryShell,
114
155
  PrototypeGalleryHeader,
115
156
  PrototypeGalleryPage,
157
+ PrototypeGalleryPageLayout,
116
158
  } from "./components/shell/prototype-gallery-shell";
117
159
 
118
160
  export { screenshotSrc } from "./lib/prototypes/screenshot-src";
@@ -225,8 +267,11 @@ export {
225
267
  PROTOTYPE_PREVIEW_STAGE_ID,
226
268
  PROTOTYPE_ROOT_ID,
227
269
  PROTOTYPE_SCREENSHOT_ATTR,
270
+ PROTOTYPE_SOURCE_SURFACE_ATTR,
228
271
  PROTOTYPE_VIEWPORT_ID,
229
272
  getPrototypeChromeRoot,
273
+ getPrototypeDialogPortalContainer,
274
+ getPrototypeToolDialogPortalContainer,
230
275
  getPrototypePreviewStage,
231
276
  getPrototypeScreenshotRoot,
232
277
  } from "./lib/tool-portal";
@@ -246,15 +291,6 @@ export {
246
291
  useChangelogMetaOptional,
247
292
  } from "./lib/prototypes/changelog-meta-context";
248
293
 
249
- export {
250
- prototypeDesignExplorationDefaultRedisKey,
251
- parseDesignExplorationDefault,
252
- isValidDesignExplorationDefaultRecord,
253
- } from "./lib/prototypes/design-exploration-default";
254
- export type { DesignExplorationDefaultRecord } from "./lib/prototypes/design-exploration-default";
255
- export { createDesignExplorationDefaultStorageAdapter } from "./lib/prototypes/design-exploration-default-storage";
256
- export { useDesignExplorationDefault } from "./lib/prototypes/use-design-exploration-default";
257
-
258
294
  export {
259
295
  filterAnnotationsByChannel,
260
296
  getChangelogRootAnnotations,
@@ -276,10 +312,18 @@ export type { PrototypeSpecPanelContentProps } from "./components/prototypes/pro
276
312
  export {
277
313
  PR_TARGET_HIGHLIGHT_BORDER,
278
314
  PR_TARGET_HIGHLIGHT_FILL,
315
+ PR_SPLIT_WIREFRAME_FRAME_CLASS,
279
316
  } from "./lib/pr-split/pr-split-highlight";
280
317
 
281
318
  export { buildPrSplitPrototypeUrl } from "./lib/pr-split/build-pr-split-prototype-url";
282
319
  export { buildPrAgentPrompt } from "./lib/pr-split/build-pr-agent-prompt";
320
+ export {
321
+ buildPrSplitPlanPrompt,
322
+ buildPrSplitPlanCopyText,
323
+ } from "./lib/pr-split/build-pr-split-plan-prompt";
324
+ export type { PrSplitPlanPromptOptions } from "./lib/pr-split/build-pr-split-plan-prompt";
325
+
326
+ export { PrototypeSpecPanelEmptyState } from "./components/prototypes/prototype-spec-panel-empty-state";
283
327
 
284
328
  export type {
285
329
  PrSplitEntry,