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,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
+ }
@@ -1,34 +1,51 @@
1
1
  "use client";
2
2
 
3
+ import { PrototypePluginUpdatePopup } from "@prototype/components/shell/prototype-plugin-update-popup";
3
4
  import { usePathname } from "next/navigation";
4
5
  import type { ComponentType, ReactNode } from "react";
5
6
 
6
7
  type PrototypeSiteLayoutClientProps = {
7
8
  children: ReactNode;
8
- galleryShell: ComponentType<{ children: ReactNode }>;
9
+ galleryShell: ComponentType<{
10
+ children: ReactNode;
11
+ sourceDirectoryName?: string;
12
+ sourcePath?: string;
13
+ }>;
14
+ sourceDirectoryName?: string;
15
+ sourcePath?: string;
9
16
  toaster: ReactNode;
10
17
  };
11
18
 
12
19
  export function PrototypeSiteLayoutClient({
13
20
  children,
14
21
  galleryShell: GalleryShell,
22
+ sourceDirectoryName,
23
+ sourcePath,
15
24
  toaster,
16
25
  }: PrototypeSiteLayoutClientProps) {
17
26
  const pathname = usePathname();
18
- const isGalleryHome = pathname === "/";
27
+ const isGalleryRoute =
28
+ pathname === "/" || pathname.startsWith("/component-library");
19
29
 
20
- if (isGalleryHome) {
30
+ if (isGalleryRoute) {
21
31
  return (
22
- <>
23
- <GalleryShell>{children}</GalleryShell>
32
+ <div className="bg-[var(--bg-main)] flex h-svh min-h-svh w-full flex-col overflow-hidden">
33
+ <GalleryShell
34
+ sourceDirectoryName={sourceDirectoryName}
35
+ sourcePath={sourcePath}
36
+ >
37
+ {children}
38
+ </GalleryShell>
39
+ <PrototypePluginUpdatePopup />
24
40
  {toaster}
25
- </>
41
+ </div>
26
42
  );
27
43
  }
28
44
 
29
45
  return (
30
46
  <div className="bg-[var(--bg-ground)] flex h-svh overflow-hidden">
31
47
  {children}
48
+ <PrototypePluginUpdatePopup />
32
49
  {toaster}
33
50
  </div>
34
51
  );
@@ -1,3 +1,6 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+
1
4
  import type { NextConfig } from "next";
2
5
 
3
6
  export type WithPrototypeOptions = {
@@ -5,16 +8,67 @@ export type WithPrototypeOptions = {
5
8
  nextConfig?: NextConfig;
6
9
  };
7
10
 
11
+ /** Package root — resolved lazily so this module is safe if ever pulled into a bundle. */
12
+ function getPackageRoot(): string {
13
+ return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
14
+ }
15
+
16
+ function getPrototypeInternalAlias() {
17
+ return {
18
+ "@prototype": path.join(getPackageRoot(), "src"),
19
+ } as const;
20
+ }
21
+
8
22
  export function withPrototype(
9
23
  options: WithPrototypeOptions = {},
10
24
  ): NextConfig {
11
25
  const { nextConfig = {} } = options;
26
+ const prototypeInternalAlias = getPrototypeInternalAlias();
27
+
28
+ const existingTurbopack = nextConfig.turbopack ?? {};
29
+ const existingResolveAlias = existingTurbopack.resolveAlias ?? {};
12
30
 
13
31
  return {
14
32
  ...nextConfig,
33
+ // Next.js 16 blocks cross-origin dev asset/HMR requests by default.
34
+ // Include ngrok (mobile testing) and common local host aliases (Glass browser, 127.0.0.1).
35
+ allowedDevOrigins: [
36
+ ...(nextConfig.allowedDevOrigins ?? []),
37
+ "127.0.0.1",
38
+ "[::1]",
39
+ "*.ngrok-free.app",
40
+ "*.ngrok-free.dev",
41
+ "*.ngrok.app",
42
+ "*.ngrok.io",
43
+ "*.ngrok.dev",
44
+ ],
15
45
  transpilePackages: [
16
46
  ...(nextConfig.transpilePackages ?? []),
17
47
  "proto-plugin",
18
48
  ],
49
+ turbopack: {
50
+ ...existingTurbopack,
51
+ resolveAlias: {
52
+ ...existingResolveAlias,
53
+ ...prototypeInternalAlias,
54
+ },
55
+ },
56
+ webpack: (config, ctx) => {
57
+ config.resolve ??= {};
58
+ config.resolve.alias = {
59
+ ...config.resolve.alias,
60
+ ...prototypeInternalAlias,
61
+ };
62
+ return nextConfig.webpack?.(config, ctx) ?? config;
63
+ },
64
+ images: {
65
+ ...nextConfig.images,
66
+ localPatterns: [
67
+ {
68
+ pathname: "/prototypes/screenshots/**",
69
+ },
70
+ ...(nextConfig.images?.localPatterns ?? []),
71
+ ],
72
+ },
19
73
  };
20
74
  }
package/src/index.ts CHANGED
@@ -1,7 +1,14 @@
1
1
  export { definePrototypeConfig } from "./config/define-prototype-config";
2
+ // NOTE: `createPrototypeGalleryPage` is server-only (reads the screenshot manifest
3
+ // from disk) and lives in `proto-plugin/server`. Keeping it out of this barrel is
4
+ // what lets client prototype components import from `proto-plugin` safely.
5
+ export { createPrototypeComponentLibraryPage } from "./config/create-prototype-component-library-page";
2
6
  export { createPrototypePage } from "./config/create-prototype-page";
7
+ export { createPrototypeStateMapPage } from "./config/create-prototype-state-map-page";
3
8
  export { createPrototypeSiteLayout } from "./config/create-prototype-site-layout";
4
- export { withPrototype } from "./config/with-prototype";
9
+ // NOTE: `withPrototype` uses Node path APIs and belongs in `proto-plugin/with-prototype`
10
+ // (next.config.ts only). Do not re-export it here — the barrel is imported by client
11
+ // prototype components and must stay browser-safe.
5
12
 
6
13
  export {
7
14
  createPrototypeRegistry,
@@ -44,26 +51,76 @@ export type { PrototypeVercelPreviewModalProps } from "./components/prototypes/p
44
51
  export { PrototypeTweaks } from "./components/prototypes/prototype-tweaks";
45
52
  export type { PrototypeTweakConfig } from "./components/prototypes/prototype-tweaks";
46
53
 
54
+ export {
55
+ PROTOTYPE_PAGE_CLASS,
56
+ PROTOTYPE_SCROLL_CONTAINER_CLASS,
57
+ PROTOTYPE_VIEW_SHELL_CLASS,
58
+ } from "./lib/prototypes/prototype-layout-classes";
59
+
47
60
  export { usePrototypeComments } from "./lib/prototypes/use-prototype-comments";
48
61
 
62
+ export {
63
+ SHARE_TARGET_PARAM,
64
+ SHARE_STATE_PARAM,
65
+ SHARE_COMMENT_PARAM,
66
+ encodeShareState,
67
+ decodeShareState,
68
+ readShareLinkParams,
69
+ } from "./lib/prototypes/prototype-share-link";
70
+ export type { ShareLinkParams } from "./lib/prototypes/prototype-share-link";
71
+
49
72
  export {
50
73
  usePersistPrototypeLiveState,
51
74
  readPersistedPrototypeLiveState,
52
75
  } from "./lib/prototypes/use-persisted-local-state";
53
76
 
54
- export type { DesignExplorationConfig } from "./lib/prototypes/design-exploration-types";
77
+ export type {
78
+ DesignExplorationBaselineOption,
79
+ DesignExplorationConfig,
80
+ DesignExplorationRationale,
81
+ MobbinReference,
82
+ } from "./lib/prototypes/design-exploration-types";
55
83
 
56
84
  export {
57
85
  buildCreateDesignExplorationCopyText,
58
86
  buildCreateDesignExplorationPrompt,
87
+ buildMoreDesignExplorationVariantsCopyText,
88
+ buildMoreDesignExplorationVariantsPrompt,
59
89
  buildResetBriefDefaultsPrompt,
90
+ DESIGN_EXPLORATION_BASELINE_LABEL,
60
91
  getAdjacentDesignExplorationVariant,
92
+ getDesignExplorationDisplayOptions,
93
+ getDesignExplorationVariantOptions,
94
+ resolveDesignExplorationBaselineOption,
61
95
  } from "./lib/prototypes/design-exploration-types";
62
96
 
97
+ export { buildDesignExplorationRenderers } from "./lib/prototypes/build-design-exploration-renderers";
98
+
99
+ export {
100
+ DesignExplorationVariantPreviewShell,
101
+ type DesignExplorationVariantPreviewLayout,
102
+ type DesignExplorationVariantPreviewShellProps,
103
+ } from "./components/prototypes/design-exploration-variant-preview-shell";
104
+
63
105
  export {
64
106
  buildCreatePreviewStateCopyText,
65
107
  buildCreatePreviewStatePrompt,
66
108
  } from "./lib/prototypes/prototype-preview-state-types";
109
+
110
+ export {
111
+ buildCreatePrototypeCopyText,
112
+ buildCreatePrototypePrompt,
113
+ suggestPrototypeSlug,
114
+ } from "./lib/prototypes/create-prototype-prompt";
115
+
116
+ export {
117
+ buildPopulateComponentLibraryCopyText,
118
+ buildPopulateComponentLibraryPrompt,
119
+ } from "./lib/prototypes/populate-component-library-prompt";
120
+ export {
121
+ buildLinkSourceCopyText,
122
+ buildLinkSourcePrompt,
123
+ } from "./lib/prototypes/link-source-prompt";
67
124
  export type { PrototypePreviewState } from "./lib/prototypes/prototype-preview-state-types";
68
125
 
69
126
  export {
@@ -113,6 +170,7 @@ export {
113
170
  PrototypeGalleryShell,
114
171
  PrototypeGalleryHeader,
115
172
  PrototypeGalleryPage,
173
+ PrototypeGalleryPageLayout,
116
174
  } from "./components/shell/prototype-gallery-shell";
117
175
 
118
176
  export { screenshotSrc } from "./lib/prototypes/screenshot-src";
@@ -225,8 +283,11 @@ export {
225
283
  PROTOTYPE_PREVIEW_STAGE_ID,
226
284
  PROTOTYPE_ROOT_ID,
227
285
  PROTOTYPE_SCREENSHOT_ATTR,
286
+ PROTOTYPE_SOURCE_SURFACE_ATTR,
228
287
  PROTOTYPE_VIEWPORT_ID,
229
288
  getPrototypeChromeRoot,
289
+ getPrototypeDialogPortalContainer,
290
+ getPrototypeToolDialogPortalContainer,
230
291
  getPrototypePreviewStage,
231
292
  getPrototypeScreenshotRoot,
232
293
  } from "./lib/tool-portal";
@@ -246,15 +307,6 @@ export {
246
307
  useChangelogMetaOptional,
247
308
  } from "./lib/prototypes/changelog-meta-context";
248
309
 
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
310
  export {
259
311
  filterAnnotationsByChannel,
260
312
  getChangelogRootAnnotations,
@@ -276,10 +328,18 @@ export type { PrototypeSpecPanelContentProps } from "./components/prototypes/pro
276
328
  export {
277
329
  PR_TARGET_HIGHLIGHT_BORDER,
278
330
  PR_TARGET_HIGHLIGHT_FILL,
331
+ PR_SPLIT_WIREFRAME_FRAME_CLASS,
279
332
  } from "./lib/pr-split/pr-split-highlight";
280
333
 
281
334
  export { buildPrSplitPrototypeUrl } from "./lib/pr-split/build-pr-split-prototype-url";
282
335
  export { buildPrAgentPrompt } from "./lib/pr-split/build-pr-agent-prompt";
336
+ export {
337
+ buildPrSplitPlanPrompt,
338
+ buildPrSplitPlanCopyText,
339
+ } from "./lib/pr-split/build-pr-split-plan-prompt";
340
+ export type { PrSplitPlanPromptOptions } from "./lib/pr-split/build-pr-split-plan-prompt";
341
+
342
+ export { PrototypeSpecPanelEmptyState } from "./components/prototypes/prototype-spec-panel-empty-state";
283
343
 
284
344
  export type {
285
345
  PrSplitEntry,
@@ -0,0 +1,35 @@
1
+ import { toast } from "sonner";
2
+
3
+ function copyTextWithExecCommand(text: string): boolean {
4
+ try {
5
+ const textarea = document.createElement("textarea");
6
+ textarea.value = text;
7
+ textarea.setAttribute("readonly", "");
8
+ textarea.style.position = "fixed";
9
+ textarea.style.left = "-9999px";
10
+ textarea.style.top = "0";
11
+ document.body.appendChild(textarea);
12
+ textarea.select();
13
+ const ok = document.execCommand("copy");
14
+ document.body.removeChild(textarea);
15
+ return ok;
16
+ } catch {
17
+ return false;
18
+ }
19
+ }
20
+
21
+ export async function copyTextWithAttachments(text: string): Promise<boolean> {
22
+ try {
23
+ await navigator.clipboard.writeText(text);
24
+ return true;
25
+ } catch {
26
+ if (copyTextWithExecCommand(text)) {
27
+ return true;
28
+ }
29
+
30
+ toast.error("Copy error", {
31
+ description: "Failed to copy prompt to clipboard",
32
+ });
33
+ return false;
34
+ }
35
+ }
@@ -0,0 +1,12 @@
1
+ /** Last path segment of SOURCE_PATH for gallery chrome, e.g. `/path/to/proto-site` → `proto-site`. */
2
+ export function formatSourceDirectoryName(
3
+ sourcePath: string | undefined,
4
+ ): string | undefined {
5
+ const trimmed = sourcePath?.trim();
6
+ if (!trimmed) return undefined;
7
+
8
+ const normalized = trimmed.replace(/[/\\]+$/, "");
9
+ const segments = normalized.split(/[/\\]/);
10
+ const name = segments.at(-1);
11
+ return name || undefined;
12
+ }
@@ -0,0 +1,19 @@
1
+ /** Interactive targets inside review/platform sidebars that should show pointer. */
2
+ export const SIDEBAR_INTERACTIVE_POINTER_SELECTOR =
3
+ ":is(button:not(:disabled), [role='button']:not([aria-disabled='true']), a[href], summary, label:has([role='switch']), label:has(input[type='checkbox']))";
4
+
5
+ export const SIDEBAR_ROOT_SELECTORS = [
6
+ "[data-prototype-review-sidebar]",
7
+ "[data-comments-sidebar]",
8
+ "[data-platform-sidebar]",
9
+ "#prototype-comments-sidebar-root",
10
+ ] as const;
11
+
12
+ export function buildSidebarInteractivePointerCss(options?: {
13
+ important?: boolean;
14
+ }): string {
15
+ const suffix = options?.important ? " !important" : "";
16
+ return SIDEBAR_ROOT_SELECTORS.map(
17
+ (root) => `${root} ${SIDEBAR_INTERACTIVE_POINTER_SELECTOR}`,
18
+ ).join(",\n") + ` { cursor: pointer${suffix}; }`;
19
+ }
@@ -0,0 +1,81 @@
1
+ export type PrSplitPlanPromptOptions = {
2
+ slug: string;
3
+ origin?: string;
4
+ sourceRepo?: string;
5
+ sourceWorkPath?: string;
6
+ };
7
+
8
+ export function buildPrSplitPlanPrompt({
9
+ slug,
10
+ origin = "http://localhost:1985",
11
+ sourceRepo = "calcom/cal.diy",
12
+ sourceWorkPath = "apps/web",
13
+ }: PrSplitPlanPromptOptions): string {
14
+ const prototypePath = `src/prototypes/${slug}/`;
15
+ const configPath = `${prototypePath}_components/pr-split-config.ts`;
16
+ const wireframesPath = `${prototypePath}_components/pr-split-wireframes.tsx`;
17
+ const previewUrl = `${origin}/prototypes/${slug}?reviewPanel=spec`;
18
+
19
+ return [
20
+ `Create a PR split spec for the \`${slug}\` prototype. **First identify everything that changed**, then propose a merge-ordered PR plan and implement the spec in this repo.`,
21
+ "",
22
+ "## Phase 1 — Audit what changed",
23
+ "",
24
+ "1. Read the prototype at `" + prototypePath + "` — especially `index.tsx`, any `*-preview-states.ts`, all `*-design-exploration-config.tsx` files, mock data, and `component-ids.ts`.",
25
+ "2. For each design exploration, compare the **baseline** option (marked **Original** in the brief) to the **default variant** in code (`DEFAULT_*_VARIANT` constants). Baselines = production / pre-exploration UI; defaults = chosen shipping direction.",
26
+ "3. Compare against production in the source app (`SOURCE_PATH` / `source/` symlink, repo `" + sourceRepo + "`). Find the real page under `" + sourceWorkPath + "` and list layout, behavior, and data-model deltas.",
27
+ "4. Run `git log` and `git diff` on `" + prototypePath + "` to capture the full change history (including uncommitted work).",
28
+ "5. Read preview states in `*-preview-states.ts` — each distinct reviewer mode may map to a PR slice.",
29
+ "6. Note explorations that should **stay in the design brief only** (alternate layout or entry-point variants not chosen for shipping).",
30
+ "",
31
+ "## Phase 2 — Plan the PR breakdown",
32
+ "",
33
+ "Propose **3–7 small, merge-ordered PRs** for `" + sourceRepo + "`. For each PR, define:",
34
+ "",
35
+ "- `order`, `title`, `description` (2–3 sentences of source-repo work)",
36
+ "- `size` (`Small` | `Medium` | `Large`)",
37
+ "- `targetId` — one registered id from `component-ids.ts`",
38
+ "- `wireframeId` — key for a full-page schematic wireframe renderer",
39
+ "- `liveState` — restores the prototype preview for that slice",
40
+ "- Dependencies on earlier PRs in the series",
41
+ "",
42
+ "Grouping guidance:",
43
+ "- Ship data / filter logic before UI entry points that depend on it",
44
+ "- Keep the primary layout change as its own PR",
45
+ "- Empty states, modals, and polish can follow once foundation PRs land",
46
+ "- Do not bundle unrelated design-brief alternates into shipping PRs",
47
+ "",
48
+ "Present the plan as a numbered list with a short dependency diagram before implementing.",
49
+ "",
50
+ "## Phase 3 — Implement the PR split spec (this repo)",
51
+ "",
52
+ "Follow `packages/prototype/AGENTS.md` → **PR split specs**:",
53
+ "",
54
+ "1. Create `" + configPath + "` with `PR_SPLIT_CONFIG` and `PR_SPLIT_ENTRIES`",
55
+ " - `slug: \"" + slug + "\"`",
56
+ " - `sourceRepo: \"" + sourceRepo + "\"`, `sourceWorkPath: \"" + sourceWorkPath + "\"`",
57
+ " - `writeEntryToSearchParams` restores each entry's `liveState`",
58
+ " - `scopeNote` guardrail: do not ship unchosen design-brief variants",
59
+ "2. Create `" + wireframesPath + "` — full-page schematic wireframes (see **Wireframe rules** below)",
60
+ "3. Wire `" + prototypePath + "index.tsx` — parse/sync `?prSplit=`, call `setSpecContent(<PrototypeSpecPanelContent ...>)`, `handlePrNavigate` with `focusShareTarget`",
61
+ "4. Update `component-ids.ts` — add `spec-panel-content`, wireframe ids, etc.",
62
+ "5. Verify each PR card restores the correct prototype state and highlights the right component",
63
+ "",
64
+ "### Wireframe rules (mandatory)",
65
+ "",
66
+ "Each PR card preview must show the **full prototype page shell** — sidebar, header/toolbar, and main content area as reviewers see them in the live preview. Do **not** draw cropped excerpts, zoomed-in component slices, or short strips that only show the changed region.",
67
+ "",
68
+ "- Scaffold every wireframe from the prototype's real page layout (match the shell layout component)",
69
+ "- Use low-fidelity `Wire` blocks; apply `PR_TARGET_HIGHLIGHT_BORDER` / `PR_TARGET_HIGHLIGHT_FILL` only on the PR target",
70
+ "- Fill the card preview area: wrap the wireframe root in `PR_SPLIT_WIREFRAME_FRAME_CLASS` (`aspect-video w-full overflow-hidden`) so the schematic reads as a full screen, not a tiny excerpt",
71
+ "- The highlight shows where the slice lands on the page; surrounding chrome provides context",
72
+ "",
73
+ "Prototype preview: " + previewUrl,
74
+ "",
75
+ "After the spec is wired, summarize the final PR sequence for the user. Do not open source PRs until the plan is approved.",
76
+ ].join("\n");
77
+ }
78
+
79
+ export function buildPrSplitPlanCopyText(options: PrSplitPlanPromptOptions): string {
80
+ return buildPrSplitPlanPrompt(options);
81
+ }
@@ -4,3 +4,6 @@ export const PR_TARGET_HIGHLIGHT_BORDER =
4
4
 
5
5
  export const PR_TARGET_HIGHLIGHT_FILL =
6
6
  "color-mix(in srgb, var(--prototype-comment-color-blue) 6%, transparent)";
7
+
8
+ /** Root wrapper for PR split wireframes — full-page schematic, not a cropped excerpt. */
9
+ export const PR_SPLIT_WIREFRAME_FRAME_CLASS = "aspect-video w-full overflow-hidden";
@@ -1,4 +1,5 @@
1
1
  import { getElementClasses } from "./element-identification";
2
+ import { getPrototypeScreenshotRoot } from "@prototype/lib/tool-portal";
2
3
 
3
4
  export const PROTOTYPE_TARGET_ATTR = "data-prototype-target";
4
5
  const PROTOTYPE_SCREENSHOT_ROOT = "[data-prototype-screenshot]";
@@ -37,9 +38,17 @@ export function getCommentsSidebarWidth(): number {
37
38
  }
38
39
 
39
40
  export function getCommentsSidebarInset(): number {
40
- if (!document.documentElement.hasAttribute("data-comments-sidebar-open")) return 0;
41
- // Inline sidebar already shrinks the prototype content area — no coordinate offset needed.
42
- if (document.documentElement.hasAttribute("data-comments-sidebar-inline")) return 0;
41
+ const isOpen =
42
+ document.documentElement.hasAttribute("data-comments-sidebar-open") ||
43
+ document.documentElement.hasAttribute("data-prototype-review-sidebar-open");
44
+ if (!isOpen) return 0;
45
+
46
+ const isInline =
47
+ document.documentElement.hasAttribute("data-comments-sidebar-inline") ||
48
+ document.documentElement.hasAttribute("data-prototype-review-sidebar-inline") ||
49
+ getPrototypeScreenshotRoot() != null;
50
+ if (isInline) return 0;
51
+
43
52
  return getCommentsSidebarWidth();
44
53
  }
45
54
 
@@ -1,4 +1,5 @@
1
1
  import { closestCrossingShadow } from "./element-identification";
2
+ import { buildSidebarInteractivePointerCss } from "@prototype/lib/platform-ui/sidebar-interactive-cursor";
2
3
 
3
4
  /** DOM regions where comment capture must not attach (plugin chrome, not prototype UI). */
4
5
  export const COMMENT_CAPTURE_BLOCKED_SELECTORS = [
@@ -21,6 +22,28 @@ export function isCommentCaptureBlockedTarget(target: HTMLElement): boolean {
21
22
  );
22
23
  }
23
24
 
25
+ /** Plugin chrome where interacting should exit comment mode (not the prototype canvas). */
26
+ export const COMMENT_MODE_CHROME_SELECTORS = [
27
+ "[data-prototype-review-trigger]",
28
+ "[data-prototype-review-sidebar]",
29
+ "[data-prototype-review-panel]",
30
+ "[data-feedback-toolbar]",
31
+ "[data-comment-capture-toolbar]",
32
+ "[data-comments-sidebar]",
33
+ "#prototype-comments-sidebar-root",
34
+ ] as const;
35
+
36
+ export const COMMENT_MODE_TOGGLE_SELECTOR = "[data-prototype-comment-mode-toggle]";
37
+
38
+ export function isCommentModeChromeInteraction(target: HTMLElement): boolean {
39
+ if (closestCrossingShadow(target, COMMENT_MODE_TOGGLE_SELECTOR) != null) {
40
+ return false;
41
+ }
42
+ return COMMENT_MODE_CHROME_SELECTORS.some(
43
+ (selector) => closestCrossingShadow(target, selector) != null,
44
+ );
45
+ }
46
+
24
47
  /** CSS :not() chain for descendants that should not receive comment-mode crosshair. */
25
48
  export function buildCommentCaptureCursorExclusionCss(): string {
26
49
  return COMMENT_CAPTURE_BLOCKED_SELECTORS.flatMap((selector) => [
@@ -43,5 +66,6 @@ export function buildCommentCaptureCursorStyles(): string {
43
66
  ${blockedCursorReset} {
44
67
  cursor: auto !important;
45
68
  }
69
+ ${buildSidebarInteractivePointerCss({ important: true })}
46
70
  `;
47
71
  }