proto-plugin 0.1.2 → 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 (32) hide show
  1. package/package.json +2 -1
  2. package/src/components/prototypes/prototype-change-log-panel.tsx +55 -55
  3. package/src/components/prototypes/prototype-comment-provider.tsx +0 -1
  4. package/src/components/prototypes/prototype-review-chrome.tsx +21 -2
  5. package/src/components/prototypes/prototype-review-sidebar.tsx +0 -13
  6. package/src/components/shell/prototype-gallery-nav.tsx +3 -3
  7. package/src/components/shell/prototype-gallery-shell.tsx +4 -4
  8. package/src/components/shell/prototype-plugin-update-popup.tsx +74 -0
  9. package/src/components/ui/button.tsx +1 -1
  10. package/src/components/ui/dialog.tsx +3 -3
  11. package/src/components/ui/dropdown-menu.tsx +2 -2
  12. package/src/components/ui/input.tsx +1 -1
  13. package/src/components/ui/label.tsx +1 -1
  14. package/src/components/ui/option-select.tsx +3 -3
  15. package/src/components/ui/scroll-area.tsx +1 -1
  16. package/src/components/ui/separator.tsx +1 -1
  17. package/src/components/ui/slider.tsx +1 -1
  18. package/src/components/ui/switch.tsx +1 -1
  19. package/src/components/ui/tabs.tsx +1 -1
  20. package/src/components/ui/toggle-group.tsx +1 -1
  21. package/src/components/ui/tooltip.tsx +2 -2
  22. package/src/config/prototype-site-layout-client.tsx +3 -0
  23. package/src/config/with-prototype.ts +33 -0
  24. package/src/index.ts +18 -2
  25. package/src/lib/prototypes/changelog-description-prompt.ts +58 -0
  26. package/src/lib/prototypes/proto-plugin-version.ts +19 -0
  27. package/src/lib/prototypes/upgrade-proto-plugin-prompt.ts +40 -0
  28. package/src/lib/prototypes/use-proto-plugin-version-check.ts +67 -0
  29. package/src/server/create-host-api-route.ts +7 -0
  30. package/src/server/proto-plugin-version-route.ts +145 -0
  31. package/src/server.ts +2 -0
  32. package/src/styles/globals.css +17 -17
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "proto-plugin",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org"
@@ -27,6 +27,7 @@
27
27
  "./app": "./src/app.ts",
28
28
  "./with-prototype": "./src/config/with-prototype.ts",
29
29
  "./server": "./src/server.ts",
30
+ "./ui/*": "./src/components/ui/*.tsx",
30
31
  "./styles/globals.css": "./src/styles/globals.css"
31
32
  },
32
33
  "scripts": {
@@ -4,20 +4,20 @@ import { filterAnnotationsByChannel } from "@prototype/lib/prototype-comments/co
4
4
  import { useCommentStore } from "@prototype/lib/prototype-comments/react/CommentProvider";
5
5
  import { useCommentCaptureBridgeOptional } from "@prototype/lib/prototype-comments/react/CommentCaptureToolbar";
6
6
  import { CommentsGrid } from "@prototype/lib/prototype-comments/ui/CommentsGrid";
7
- import { useChangelogMeta } from "@prototype/lib/prototypes/changelog-meta-context";
7
+ import { buildChangelogDescriptionCopyText } from "@prototype/lib/prototypes/changelog-description-prompt";
8
8
  import { usePrototypeReview } from "@prototype/lib/prototypes/prototype-review-context";
9
+ import {
10
+ prototypeReviewPreferenceKey,
11
+ usePersistedLocalString,
12
+ } from "@prototype/lib/prototypes/use-persisted-local-state";
9
13
  import { ToolbarIconButton } from "@prototype/components/platform-ui/toolbar-icon-button";
14
+ import { Button } from "@prototype/components/ui/button";
10
15
  import { Label } from "@prototype/components/ui/label";
11
16
  import { Switch } from "@prototype/components/ui/switch";
12
- import { Plus, Save } from "lucide-react";
17
+ import useCopyToClipboard from "@prototype/lib/use-copy-to-clipboard";
18
+ import { Plus } from "lucide-react";
13
19
  import { cn } from "@prototype/lib/utils";
14
- import {
15
- useCallback,
16
- useEffect,
17
- useMemo,
18
- useState,
19
- type ChangeEvent,
20
- } from "react";
20
+ import { useMemo } from "react";
21
21
 
22
22
  import { PrototypeReferenceDocs } from "./prototype-reference-docs";
23
23
  import { PrototypeCommentStorageEmptyState } from "./prototype-comment-storage-empty-state";
@@ -108,63 +108,63 @@ export function PrototypeChangeLogPanel({
108
108
  storageReady,
109
109
  storageConfigured,
110
110
  } = useCommentStore();
111
- const { overview, ready: metaReady, error: metaError, setOverview } =
112
- useChangelogMeta();
113
- const [draftOverview, setDraftOverview] = useState(overview);
111
+ const review = usePrototypeReview();
112
+ const { value: description, updateValue: updateDescription } =
113
+ usePersistedLocalString(
114
+ prototypeReviewPreferenceKey(review.slug, "changelog-description-brief"),
115
+ "",
116
+ );
117
+ const {
118
+ copy: copyDescriptionPrompt,
119
+ icon: copyDescriptionIcon,
120
+ isCopied: isDescriptionCopied,
121
+ } = useCopyToClipboard();
114
122
  const changelogAnnotations = useMemo(
115
123
  () => filterAnnotationsByChannel(annotations, "changelog"),
116
124
  [annotations],
117
125
  );
126
+ const canCopyDescription = description.trim().length > 0;
118
127
 
119
- useEffect(() => {
120
- if (metaReady) {
121
- setDraftOverview(overview);
122
- }
123
- }, [metaReady, overview]);
124
-
125
- const isOverviewDirty = draftOverview !== overview;
128
+ const handleCopyDescriptionPrompt = () => {
129
+ if (!canCopyDescription) return;
126
130
 
127
- const handleOverviewChange = useCallback(
128
- (event: ChangeEvent<HTMLTextAreaElement>) => {
129
- setDraftOverview(event.target.value);
130
- },
131
- [],
132
- );
131
+ const origin =
132
+ typeof window !== "undefined" ? window.location.origin : undefined;
133
133
 
134
- const handleSaveOverview = useCallback(() => {
135
- setOverview(draftOverview);
136
- }, [draftOverview, setOverview]);
134
+ copyDescriptionPrompt(
135
+ buildChangelogDescriptionCopyText({
136
+ slug: review.slug,
137
+ description,
138
+ origin,
139
+ }),
140
+ );
141
+ };
137
142
 
138
143
  return (
139
144
  <div className="flex flex-col">
140
145
  <section className={CHANGELOG_SECTION_CLASS}>
141
- <div className="flex items-center justify-between gap-2">
142
- <span className={CHANGELOG_SECTION_TITLE_CLASS}>Description</span>
143
- {metaReady && !metaError ? (
144
- <ToolbarIconButton
145
- onClick={handleSaveOverview}
146
- disabled={!isOverviewDirty}
147
- aria-label="Save description"
148
- >
149
- <Save size={16} strokeWidth={2} aria-hidden />
150
- </ToolbarIconButton>
151
- ) : null}
152
- </div>
153
- {!metaReady ? (
154
- <p className="text-muted-foreground text-xs">Loading description…</p>
155
- ) : metaError ? (
156
- <p className="text-destructive text-xs">{metaError}</p>
157
- ) : (
158
- <textarea
159
- id="prototype-changelog-overview"
160
- value={draftOverview}
161
- onChange={handleOverviewChange}
162
- rows={1}
163
- placeholder="Summarize what changed and why…"
164
- aria-label="Description"
165
- className={CHANGELOG_OVERVIEW_FIELD_CLASS}
166
- />
167
- )}
146
+ <span className={CHANGELOG_SECTION_TITLE_CLASS}>Description</span>
147
+ <textarea
148
+ id="prototype-changelog-overview"
149
+ value={description}
150
+ onChange={(event) => updateDescription(event.target.value)}
151
+ rows={3}
152
+ placeholder="Summarize what changed and why…"
153
+ aria-label="Description"
154
+ className={CHANGELOG_OVERVIEW_FIELD_CLASS}
155
+ />
156
+ <Button
157
+ type="button"
158
+ variant="chrome"
159
+ size="sm"
160
+ className="h-8 gap-1.5 self-start"
161
+ disabled={!canCopyDescription}
162
+ onClick={handleCopyDescriptionPrompt}
163
+ aria-label="Copy description prompt"
164
+ >
165
+ {isDescriptionCopied ? "Copied prompt" : "Copy prompt"}
166
+ {copyDescriptionIcon}
167
+ </Button>
168
168
  </section>
169
169
 
170
170
  <PrototypeReferenceDocs
@@ -4,7 +4,6 @@ import type { ReactNode } from "react";
4
4
 
5
5
  import { PrototypeTargetIdProvider } from "@prototype/components/prototypes/prototype-target";
6
6
  import { CommentProvider } from "@prototype/lib/prototype-comments/react/CommentProvider";
7
- import { ChangelogMetaProvider } from "@prototype/lib/prototypes/changelog-meta-context";
8
7
  import type { PrototypeComponentRegistry } from "@prototype/lib/prototypes/prototype-component-registry";
9
8
  import { getPrototypeComponentRegistryForSlug } from "@prototype/lib/prototypes/create-prototype-registry";
10
9
  import {
@@ -48,6 +48,7 @@ import {
48
48
  LayoutGrid,
49
49
  MapPin,
50
50
  MessageSquare,
51
+ MessageSquarePlus,
51
52
  Monitor,
52
53
  Moon,
53
54
  Network,
@@ -250,6 +251,7 @@ function ReviewToolbarButton({
250
251
  onClick,
251
252
  hoverOnly = false,
252
253
  hoverTriggerProps,
254
+ commentModeToggle = false,
253
255
  children,
254
256
  }: {
255
257
  label: string;
@@ -260,6 +262,7 @@ function ReviewToolbarButton({
260
262
  onClick?: (event: MouseEvent<HTMLButtonElement>) => void;
261
263
  hoverOnly?: boolean;
262
264
  hoverTriggerProps?: ToolbarTooltipAnchorProps;
265
+ commentModeToggle?: boolean;
263
266
  children: ReactNode;
264
267
  }) {
265
268
  return (
@@ -272,6 +275,9 @@ function ReviewToolbarButton({
272
275
  pressed && !highlightActive && styles.toolbarButtonHovered,
273
276
  )}
274
277
  data-active={highlightActive && pressed ? "true" : undefined}
278
+ data-prototype-comment-mode-toggle={
279
+ commentModeToggle ? true : undefined
280
+ }
275
281
  aria-label={label}
276
282
  aria-pressed={pressed}
277
283
  onClick={(event) => {
@@ -1121,6 +1127,21 @@ export function PrototypeReviewChrome({
1121
1127
  </DropdownMenuContent>
1122
1128
  </DropdownMenu>
1123
1129
 
1130
+ {onToggleCommentMode ? (
1131
+ <ReviewToolbarButton
1132
+ label={
1133
+ isCommentModeActive ? "Exit comment mode" : "Add comment"
1134
+ }
1135
+ tooltip="Add comment"
1136
+ pressed={isCommentModeActive ?? false}
1137
+ highlightActive={isCommentModeActive ?? false}
1138
+ commentModeToggle
1139
+ onClick={() => onToggleCommentMode()}
1140
+ >
1141
+ <MessageSquarePlus size={16} strokeWidth={2} />
1142
+ </ReviewToolbarButton>
1143
+ ) : null}
1144
+
1124
1145
  <ReviewToolbarButton
1125
1146
  label={
1126
1147
  review.open && review.sidebarPanel === "comments"
@@ -1204,8 +1225,6 @@ export function PrototypeReviewChrome({
1204
1225
  onSelect={onSelect}
1205
1226
  selectedId={selectedId}
1206
1227
  onClose={onClose}
1207
- isCommentModeActive={isCommentModeActive}
1208
- onToggleCommentMode={onToggleCommentMode}
1209
1228
  />,
1210
1229
  sidebarPortalTarget,
1211
1230
  )
@@ -7,7 +7,6 @@ import { useCommentStore } from "@prototype/lib/prototype-comments/react/Comment
7
7
  import { CommentHeaderCount } from "@prototype/lib/prototype-comments/ui/CommentHeaderCount";
8
8
  import { CommentsGrid } from "@prototype/lib/prototype-comments/ui/CommentsGrid";
9
9
  import { usePrototypeReview } from "@prototype/lib/prototypes/prototype-review-context";
10
- import { CommentModeToggleButton } from "@prototype/lib/prototype-comments/ui/CommentModeToggleButton";
11
10
  import { Sidebar } from "@prototype/components/platform-ui/sidebar";
12
11
  import { EmptyState } from "@prototype/components/platform-ui/empty-state";
13
12
  import { PrototypeChangeLogPanel } from "@prototype/components/prototypes/prototype-change-log-panel";
@@ -20,16 +19,12 @@ type PrototypeReviewSidebarProps = {
20
19
  onSelect: (id: string) => void;
21
20
  selectedId?: string | null;
22
21
  onClose: () => void;
23
- isCommentModeActive?: boolean;
24
- onToggleCommentMode?: () => void;
25
22
  };
26
23
 
27
24
  export function PrototypeReviewSidebar({
28
25
  onSelect,
29
26
  selectedId,
30
27
  onClose,
31
- isCommentModeActive = false,
32
- onToggleCommentMode,
33
28
  }: PrototypeReviewSidebarProps) {
34
29
  const review = usePrototypeReview();
35
30
  const { annotations, deleteAnnotation, updateAnnotation, resolveAnnotation, storageError, storageReady, storageConfigured } = useCommentStore();
@@ -91,14 +86,6 @@ export function PrototypeReviewSidebar({
91
86
  />
92
87
  ) : undefined
93
88
  }
94
- headerActions={
95
- isCommentsPanel && onToggleCommentMode ? (
96
- <CommentModeToggleButton
97
- isActive={isCommentModeActive}
98
- onClick={onToggleCommentMode}
99
- />
100
- ) : undefined
101
- }
102
89
  onClose={
103
90
  isVariantsPanel
104
91
  ? review.closeVariants
@@ -5,7 +5,7 @@ import { Button } from "@prototype/components/ui/button";
5
5
  import { PrototypeLinkSourceModal } from "@prototype/components/shell/prototype-link-source-modal";
6
6
  import { cn } from "@prototype/lib/utils";
7
7
  import { usePrototypeToolTheme } from "@prototype/lib/prototypes/use-prototype-tool-theme";
8
- import { Moon, Pencil, Shapes, Sun, LayoutDashboard } from "lucide-react";
8
+ import { LibraryBig, Moon, Pencil, Shapes, Sun } from "lucide-react";
9
9
  import Link from "next/link";
10
10
  import { usePathname } from "next/navigation";
11
11
  import { useState } from "react";
@@ -19,7 +19,7 @@ const GALLERY_NAV_ITEMS = [
19
19
  {
20
20
  href: "/component-library",
21
21
  label: "Component Library",
22
- icon: LayoutDashboard,
22
+ icon: LibraryBig,
23
23
  },
24
24
  ] as const;
25
25
 
@@ -37,7 +37,7 @@ export function PrototypeGalleryNav({
37
37
  const [linkSourceOpen, setLinkSourceOpen] = useState(false);
38
38
 
39
39
  return (
40
- <aside className="border-[var(--tool-chrome-border)] bg-[var(--bg-ground)] flex h-full min-h-0 w-56 shrink-0 flex-col self-stretch overflow-hidden border-r">
40
+ <aside className="border-[var(--tool-chrome-border)] bg-[var(--bg-main)] flex h-full min-h-0 w-56 shrink-0 flex-col self-stretch overflow-hidden border-r">
41
41
  <nav aria-label="Gallery" className="flex flex-col gap-1 p-3">
42
42
  {GALLERY_NAV_ITEMS.map(({ href, label, icon: Icon }) => {
43
43
  const isActive =
@@ -21,7 +21,7 @@ export function PrototypeGalleryShell({
21
21
  return (
22
22
  <PrototypeProvider className="bg-[var(--bg-main)] flex h-full min-h-0 w-full flex-1 flex-col overflow-hidden">
23
23
  {footer ? (
24
- <div className="border-[var(--tool-chrome-border)] bg-[var(--bg-ground)] flex shrink-0 items-center justify-end border-b px-10 py-3 lg:px-16">
24
+ <div className="border-[var(--tool-chrome-border)] bg-[var(--bg-main)] flex shrink-0 items-center justify-end border-b px-10 py-3 lg:px-16">
25
25
  {footer}
26
26
  </div>
27
27
  ) : null}
@@ -31,7 +31,7 @@ export function PrototypeGalleryShell({
31
31
  sourceDirectoryName={sourceDirectoryName}
32
32
  sourcePath={sourcePath}
33
33
  />
34
- <main className="bg-[var(--bg-main)] flex min-h-0 flex-1 flex-col overflow-hidden">
34
+ <main className="bg-[var(--bg-ground)] flex min-h-0 flex-1 flex-col overflow-hidden">
35
35
  {children}
36
36
  </main>
37
37
  </div>
@@ -52,7 +52,7 @@ export function PrototypeGalleryPageLayout({
52
52
  return (
53
53
  <div className="flex h-full min-h-0 flex-1 flex-col overflow-hidden">
54
54
  {header}
55
- <div className="bg-[var(--bg-main)] min-h-0 flex-1 overflow-y-auto overscroll-y-contain">
55
+ <div className="bg-[var(--bg-ground)] min-h-0 flex-1 overflow-y-auto overscroll-y-contain">
56
56
  <div className="h-fit w-full">{children}</div>
57
57
  </div>
58
58
  </div>
@@ -75,7 +75,7 @@ export function PrototypeGalleryHeader({
75
75
  return (
76
76
  <div
77
77
  className={cn(
78
- "border-[var(--tool-chrome-border)] bg-[var(--bg-ground)] shrink-0 border-b px-10 py-6 lg:px-16",
78
+ "border-[var(--tool-chrome-border)] bg-[var(--bg-main)] shrink-0 border-b px-10 py-6 lg:px-16",
79
79
  className,
80
80
  )}
81
81
  >
@@ -0,0 +1,74 @@
1
+ "use client";
2
+
3
+ import { Button } from "@prototype/components/ui/button";
4
+ import { buildUpgradeProtoPluginCopyText } from "@prototype/lib/prototypes/upgrade-proto-plugin-prompt";
5
+ import { useProtoPluginVersionCheck } from "@prototype/lib/prototypes/use-proto-plugin-version-check";
6
+ import useCopyToClipboard from "@prototype/lib/use-copy-to-clipboard";
7
+ import { cn } from "@prototype/lib/utils";
8
+ import { X } from "lucide-react";
9
+ import { toast } from "sonner";
10
+
11
+ export function PrototypePluginUpdatePopup() {
12
+ const { status, shouldShowPopup, dismiss } = useProtoPluginVersionCheck();
13
+ const { copy, icon, isCopied } = useCopyToClipboard();
14
+
15
+ if (!shouldShowPopup || !status?.installed || !status.latest) {
16
+ return null;
17
+ }
18
+
19
+ const handleCopy = async () => {
20
+ try {
21
+ await copy(
22
+ buildUpgradeProtoPluginCopyText({
23
+ installed: status.installed!,
24
+ latest: status.latest!,
25
+ }),
26
+ );
27
+ } catch {
28
+ toast.error("Copy error", {
29
+ description: "Failed to copy upgrade prompt",
30
+ });
31
+ }
32
+ };
33
+
34
+ return (
35
+ <div
36
+ role="status"
37
+ aria-live="polite"
38
+ className={cn(
39
+ "border-border bg-card ring-border pointer-events-auto fixed bottom-4 left-4 z-[1050] flex w-[min(20rem,calc(100vw-2rem))] flex-col gap-3 rounded-xl border p-4 shadow-lg ring-[0.5px]",
40
+ )}
41
+ >
42
+ <div className="flex items-start justify-between gap-2">
43
+ <div className="min-w-0 flex-1">
44
+ <p className="text-sm font-medium text-foreground">Update proto-plugin</p>
45
+ <p className="mt-1 text-sm text-muted-foreground">
46
+ v{status.latest} is available. You&apos;re on v{status.installed}.
47
+ </p>
48
+ </div>
49
+ <button
50
+ type="button"
51
+ aria-label="Dismiss update notification"
52
+ className="text-muted-foreground hover:text-foreground shrink-0 rounded p-1 transition-colors duration-200 ease"
53
+ onClick={dismiss}
54
+ >
55
+ <X size={14} />
56
+ </button>
57
+ </div>
58
+
59
+ <Button
60
+ type="button"
61
+ variant="outline"
62
+ size="sm"
63
+ className="h-8 w-fit cursor-pointer gap-1.5"
64
+ onClick={() => {
65
+ void handleCopy();
66
+ }}
67
+ aria-label="Copy upgrade prompt"
68
+ >
69
+ {isCopied ? "Copied prompt" : "Copy prompt"}
70
+ {icon}
71
+ </Button>
72
+ </div>
73
+ );
74
+ }
@@ -4,7 +4,7 @@ import { Slot } from "@radix-ui/react-slot";
4
4
  import { cva, type VariantProps } from "class-variance-authority";
5
5
  import * as React from "react";
6
6
 
7
- import { cn } from "@prototype/lib/utils";
7
+ import { cn } from "../../lib/utils";
8
8
 
9
9
  const buttonVariants = cva(
10
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",
@@ -4,12 +4,12 @@ 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 { isCommentCaptureBlockedTarget } from "@prototype/lib/prototype-comments/core/comment-capture-blocked";
7
+ import { isCommentCaptureBlockedTarget } from "../../lib/prototype-comments/core/comment-capture-blocked";
8
8
  import {
9
9
  getPrototypeDialogPortalContainer,
10
10
  getPrototypeToolDialogPortalContainer,
11
- } from "@prototype/lib/tool-portal";
12
- import { cn } from "@prototype/lib/utils";
11
+ } from "../../lib/tool-portal";
12
+ import { cn } from "../../lib/utils";
13
13
 
14
14
  import styles from "./dialog.module.scss";
15
15
 
@@ -4,8 +4,8 @@ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
4
4
  import { Check, ChevronRight, Circle } from "lucide-react";
5
5
  import * as React from "react";
6
6
 
7
- import { getPrototypePortalContainer } from "@prototype/lib/tool-portal";
8
- import { cn } from "@prototype/lib/utils";
7
+ import { getPrototypePortalContainer } from "../../lib/tool-portal";
8
+ import { cn } from "../../lib/utils";
9
9
 
10
10
  const DropdownMenu = DropdownMenuPrimitive.Root;
11
11
  const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
@@ -1,6 +1,6 @@
1
1
  import * as React from "react";
2
2
 
3
- import { cn } from "@prototype/lib/utils";
3
+ import { cn } from "../../lib/utils";
4
4
 
5
5
  const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
6
6
  ({ className, type, ...props }, ref) => {
@@ -4,7 +4,7 @@ import * as LabelPrimitive from "@radix-ui/react-label";
4
4
  import { cva, type VariantProps } from "class-variance-authority";
5
5
  import * as React from "react";
6
6
 
7
- import { cn } from "@prototype/lib/utils";
7
+ import { cn } from "../../lib/utils";
8
8
 
9
9
  const labelVariants = cva(
10
10
  "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
@@ -3,14 +3,14 @@
3
3
  import { ChevronDown } from "lucide-react";
4
4
  import type { ReactNode } from "react";
5
5
 
6
- import { Button } from "@prototype/components/ui/button";
6
+ import { Button } from "./button";
7
7
  import {
8
8
  DropdownMenu,
9
9
  DropdownMenuContent,
10
10
  DropdownMenuItem,
11
11
  DropdownMenuTrigger,
12
- } from "@prototype/components/ui/dropdown-menu";
13
- import { cn } from "@prototype/lib/utils";
12
+ } from "./dropdown-menu";
13
+ import { cn } from "../../lib/utils";
14
14
 
15
15
  export type OptionSelectItem<T extends string = string> = {
16
16
  value: T;
@@ -3,7 +3,7 @@
3
3
  import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
4
4
  import * as React from "react";
5
5
 
6
- import { cn } from "@prototype/lib/utils";
6
+ import { cn } from "../../lib/utils";
7
7
 
8
8
  const ScrollArea = React.forwardRef<
9
9
  React.ComponentRef<typeof ScrollAreaPrimitive.Root>,
@@ -3,7 +3,7 @@
3
3
  import * as SeparatorPrimitive from "@radix-ui/react-separator";
4
4
  import * as React from "react";
5
5
 
6
- import { cn } from "@prototype/lib/utils";
6
+ import { cn } from "../../lib/utils";
7
7
 
8
8
  const Separator = React.forwardRef<
9
9
  React.ComponentRef<typeof SeparatorPrimitive.Root>,
@@ -3,7 +3,7 @@
3
3
  import * as SliderPrimitive from "@radix-ui/react-slider";
4
4
  import * as React from "react";
5
5
 
6
- import { cn } from "@prototype/lib/utils";
6
+ import { cn } from "../../lib/utils";
7
7
 
8
8
  const Slider = React.forwardRef<
9
9
  React.ComponentRef<typeof SliderPrimitive.Root>,
@@ -3,7 +3,7 @@
3
3
  import * as SwitchPrimitives from "@radix-ui/react-switch";
4
4
  import * as React from "react";
5
5
 
6
- import { cn } from "@prototype/lib/utils";
6
+ import { cn } from "../../lib/utils";
7
7
 
8
8
  const Switch = React.forwardRef<
9
9
  React.ComponentRef<typeof SwitchPrimitives.Root>,
@@ -3,7 +3,7 @@
3
3
  import * as TabsPrimitive from "@radix-ui/react-tabs";
4
4
  import * as React from "react";
5
5
 
6
- import { cn } from "@prototype/lib/utils";
6
+ import { cn } from "../../lib/utils";
7
7
 
8
8
  const Tabs = TabsPrimitive.Root;
9
9
 
@@ -4,7 +4,7 @@ import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
4
4
  import { type VariantProps, cva } from "class-variance-authority";
5
5
  import * as React from "react";
6
6
 
7
- import { cn } from "@prototype/lib/utils";
7
+ import { cn } from "../../lib/utils";
8
8
 
9
9
  const toggleVariants = cva(
10
10
  "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground",
@@ -3,8 +3,8 @@
3
3
  import * as TooltipPrimitive from "@radix-ui/react-tooltip";
4
4
  import * as React from "react";
5
5
 
6
- import { getPrototypePortalContainer } from "@prototype/lib/tool-portal";
7
- import { cn } from "@prototype/lib/utils";
6
+ import { getPrototypePortalContainer } from "../../lib/tool-portal";
7
+ import { cn } from "../../lib/utils";
8
8
 
9
9
  const TooltipProvider = TooltipPrimitive.Provider;
10
10
 
@@ -1,5 +1,6 @@
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
 
@@ -35,6 +36,7 @@ export function PrototypeSiteLayoutClient({
35
36
  >
36
37
  {children}
37
38
  </GalleryShell>
39
+ <PrototypePluginUpdatePopup />
38
40
  {toaster}
39
41
  </div>
40
42
  );
@@ -43,6 +45,7 @@ export function PrototypeSiteLayoutClient({
43
45
  return (
44
46
  <div className="bg-[var(--bg-ground)] flex h-svh overflow-hidden">
45
47
  {children}
48
+ <PrototypePluginUpdatePopup />
46
49
  {toaster}
47
50
  </div>
48
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,10 +8,25 @@ 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,
@@ -28,6 +46,21 @@ export function withPrototype(
28
46
  ...(nextConfig.transpilePackages ?? []),
29
47
  "proto-plugin",
30
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
+ },
31
64
  images: {
32
65
  ...nextConfig.images,
33
66
  localPatterns: [
package/src/index.ts CHANGED
@@ -1,10 +1,14 @@
1
1
  export { definePrototypeConfig } from "./config/define-prototype-config";
2
- export { createPrototypeGalleryPage } from "./config/create-prototype-gallery-page";
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.
3
5
  export { createPrototypeComponentLibraryPage } from "./config/create-prototype-component-library-page";
4
6
  export { createPrototypePage } from "./config/create-prototype-page";
5
7
  export { createPrototypeStateMapPage } from "./config/create-prototype-state-map-page";
6
8
  export { createPrototypeSiteLayout } from "./config/create-prototype-site-layout";
7
- 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.
8
12
 
9
13
  export {
10
14
  createPrototypeRegistry,
@@ -55,6 +59,16 @@ export {
55
59
 
56
60
  export { usePrototypeComments } from "./lib/prototypes/use-prototype-comments";
57
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
+
58
72
  export {
59
73
  usePersistPrototypeLiveState,
60
74
  readPersistedPrototypeLiveState,
@@ -63,6 +77,8 @@ export {
63
77
  export type {
64
78
  DesignExplorationBaselineOption,
65
79
  DesignExplorationConfig,
80
+ DesignExplorationRationale,
81
+ MobbinReference,
66
82
  } from "./lib/prototypes/design-exploration-types";
67
83
 
68
84
  export {
@@ -0,0 +1,58 @@
1
+ export function buildChangelogDescriptionPrompt({
2
+ slug,
3
+ description,
4
+ origin = "http://localhost:3003",
5
+ }: {
6
+ slug: string;
7
+ description: string;
8
+ origin?: string;
9
+ }): string {
10
+ const prototypeUrl = `${origin}/prototypes/${slug}`;
11
+ const trimmed = description.trim();
12
+
13
+ const lines = [
14
+ `Update the "${slug}" prototype to match the following change log overview.`,
15
+ "",
16
+ "## Description",
17
+ ];
18
+
19
+ if (trimmed) {
20
+ lines.push(trimmed);
21
+ } else {
22
+ lines.push(
23
+ "Summarize what changed in this prototype and why:",
24
+ "- What changed:",
25
+ "- Why:",
26
+ );
27
+ }
28
+
29
+ lines.push(
30
+ "",
31
+ "## Prototype",
32
+ `- Slug: \`${slug}\``,
33
+ `- Preview: ${prototypeUrl}`,
34
+ `- Work in: \`src/prototypes/${slug}/\``,
35
+ "",
36
+ "## Instructions",
37
+ "1. Review the overview and anchored changelog entries in the prototype review sidebar.",
38
+ "2. Apply any missing updates so the prototype matches the described changes.",
39
+ "3. Do not persist this overview in KV — it is prompt-only.",
40
+ );
41
+
42
+ return lines.join("\n");
43
+ }
44
+
45
+ export function buildChangelogDescriptionCopyText({
46
+ slug,
47
+ description,
48
+ origin,
49
+ }: {
50
+ slug: string;
51
+ description: string;
52
+ origin?: string;
53
+ }): string {
54
+ const prompt = buildChangelogDescriptionPrompt({ slug, description, origin });
55
+ const trimmed = description.trim();
56
+ if (!trimmed) return prompt;
57
+ return `${trimmed}\n\n---\n\n${prompt}`;
58
+ }
@@ -0,0 +1,19 @@
1
+ export type ProtoPluginVersionStatus = {
2
+ installed: string | null;
3
+ latest: string | null;
4
+ updateAvailable: boolean;
5
+ isWorkspaceLink: boolean;
6
+ repositoryUrl?: string;
7
+ };
8
+
9
+ export const PROTOTYPE_PLUGIN_VERSION_PATH = "/api/proto-plugin/version";
10
+
11
+ export async function fetchProtoPluginVersionStatus(): Promise<ProtoPluginVersionStatus> {
12
+ const response = await fetch(PROTOTYPE_PLUGIN_VERSION_PATH);
13
+
14
+ if (!response.ok) {
15
+ throw new Error("Failed to check proto-plugin version.");
16
+ }
17
+
18
+ return (await response.json()) as ProtoPluginVersionStatus;
19
+ }
@@ -0,0 +1,40 @@
1
+ export type UpgradeProtoPluginPromptOptions = {
2
+ installed: string;
3
+ latest: string;
4
+ };
5
+
6
+ export function buildUpgradeProtoPluginPrompt({
7
+ installed,
8
+ latest,
9
+ }: UpgradeProtoPluginPromptOptions): string {
10
+ return [
11
+ `Upgrade \`proto-plugin\` in this host app from \`${installed}\` to \`${latest}\`. **Do every step yourself** — the user should not run commands manually.`,
12
+ "",
13
+ "## Your job",
14
+ `Update the dependency, restart the dev server if needed, and confirm the gallery and at least one prototype still work on \`${latest}\`.`,
15
+ "",
16
+ "## Versions",
17
+ `- Installed: \`${installed}\``,
18
+ `- Latest: \`${latest}\``,
19
+ "",
20
+ "## Steps",
21
+ "1. Detect the package manager from the lockfile (`pnpm-lock.yaml`, `package-lock.json`, or `yarn.lock`).",
22
+ "2. Update the dependency:",
23
+ ` - pnpm: \`pnpm update proto-plugin\` or \`pnpm add proto-plugin@${latest}\``,
24
+ ` - npm: \`npm install proto-plugin@${latest}\``,
25
+ ` - yarn: \`yarn add proto-plugin@${latest}\``,
26
+ "3. Restart the dev server if it is already running.",
27
+ `4. Skim \`node_modules/proto-plugin/AGENTS.md\` (or the package changelog on npm) for breaking changes since \`${installed}\`.`,
28
+ "5. Open `/` and one prototype preview — confirm the gallery and review chrome load without errors.",
29
+ "",
30
+ "## Notes",
31
+ "- Do not modify prototype source under `src/prototypes/` unless AGENTS.md or the changelog requires migration steps for this upgrade.",
32
+ "- If the update fails (peer dependency conflicts, build errors), diagnose and fix before stopping.",
33
+ ].join("\n");
34
+ }
35
+
36
+ export function buildUpgradeProtoPluginCopyText(
37
+ options: UpgradeProtoPluginPromptOptions,
38
+ ): string {
39
+ return buildUpgradeProtoPluginPrompt(options);
40
+ }
@@ -0,0 +1,67 @@
1
+ "use client";
2
+
3
+ import {
4
+ fetchProtoPluginVersionStatus,
5
+ type ProtoPluginVersionStatus,
6
+ } from "@prototype/lib/prototypes/proto-plugin-version";
7
+ import { usePersistedLocalString } from "@prototype/lib/prototypes/use-persisted-local-state";
8
+ import { useCallback, useEffect, useMemo, useState } from "react";
9
+
10
+ const DISMISSED_VERSION_KEY = "prototype-review:global:dismissed-plugin-version";
11
+
12
+ export function useProtoPluginVersionCheck() {
13
+ const [status, setStatus] = useState<ProtoPluginVersionStatus | null>(null);
14
+ const [isLoading, setIsLoading] = useState(true);
15
+ const { value: dismissedVersion, updateValue: setDismissedVersion } =
16
+ usePersistedLocalString(DISMISSED_VERSION_KEY, "");
17
+
18
+ useEffect(() => {
19
+ let cancelled = false;
20
+
21
+ void (async () => {
22
+ try {
23
+ const nextStatus = await fetchProtoPluginVersionStatus();
24
+ if (!cancelled) {
25
+ setStatus(nextStatus);
26
+ }
27
+ } catch {
28
+ if (!cancelled) {
29
+ setStatus(null);
30
+ }
31
+ } finally {
32
+ if (!cancelled) {
33
+ setIsLoading(false);
34
+ }
35
+ }
36
+ })();
37
+
38
+ return () => {
39
+ cancelled = true;
40
+ };
41
+ }, []);
42
+
43
+ const dismiss = useCallback(() => {
44
+ if (status?.latest) {
45
+ setDismissedVersion(status.latest);
46
+ }
47
+ }, [setDismissedVersion, status?.latest]);
48
+
49
+ const shouldShowPopup = useMemo(() => {
50
+ if (isLoading || !status?.updateAvailable || !status.latest || !status.installed) {
51
+ return false;
52
+ }
53
+
54
+ if (!dismissedVersion) {
55
+ return true;
56
+ }
57
+
58
+ return dismissedVersion !== status.latest;
59
+ }, [dismissedVersion, isLoading, status]);
60
+
61
+ return {
62
+ status,
63
+ shouldShowPopup,
64
+ dismiss,
65
+ isLoading,
66
+ };
67
+ }
@@ -10,6 +10,7 @@ import {
10
10
  } from "./create-prototype-api-routes";
11
11
  import { createGalleryApi } from "./gallery/create-gallery-api";
12
12
  import { GET as getPrototypeStorageStatus } from "./prototype-storage-status-route";
13
+ import { GET as getProtoPluginVersion } from "./proto-plugin-version-route";
13
14
  import { GET as getPrVercelPreview } from "./pr-vercel-preview-route";
14
15
 
15
16
  type ApiRouteContext = {
@@ -42,6 +43,12 @@ export function createPrototypeApiRoute(_config: PrototypeConfig) {
42
43
  }
43
44
  }
44
45
 
46
+ if (path[0] === "proto-plugin" && path.length === 2 && path[1] === "version") {
47
+ if (method === "GET") {
48
+ return getProtoPluginVersion();
49
+ }
50
+ }
51
+
45
52
  if (path[0] === "prototypes" && path.length === 2 && path[1] === "storage-status") {
46
53
  if (method === "GET") {
47
54
  return getPrototypeStorageStatus();
@@ -0,0 +1,145 @@
1
+ import { readFileSync, lstatSync } from "fs";
2
+ import { join } from "path";
3
+
4
+ import { NextResponse } from "next/server";
5
+
6
+ const PACKAGE_NAME = "proto-plugin";
7
+ const NPM_LATEST_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
8
+ const CACHE_TTL_MS = 60 * 60 * 1000;
9
+
10
+ export type ProtoPluginVersionStatus = {
11
+ installed: string | null;
12
+ latest: string | null;
13
+ updateAvailable: boolean;
14
+ isWorkspaceLink: boolean;
15
+ repositoryUrl?: string;
16
+ };
17
+
18
+ type NpmLatestResponse = {
19
+ version?: string;
20
+ repository?: { url?: string };
21
+ };
22
+
23
+ let cachedLatest: { version: string; repositoryUrl?: string; fetchedAt: number } | null =
24
+ null;
25
+
26
+ function parseSemver(version: string): [number, number, number] | null {
27
+ const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version.trim());
28
+ if (!match) return null;
29
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
30
+ }
31
+
32
+ export function isNewerVersion(latest: string, installed: string): boolean {
33
+ const latestParts = parseSemver(latest);
34
+ const installedParts = parseSemver(installed);
35
+ if (!latestParts || !installedParts) return false;
36
+
37
+ for (let index = 0; index < 3; index += 1) {
38
+ if (latestParts[index]! > installedParts[index]!) return true;
39
+ if (latestParts[index]! < installedParts[index]!) return false;
40
+ }
41
+
42
+ return false;
43
+ }
44
+
45
+ function readInstalledVersion(cwd: string): {
46
+ installed: string | null;
47
+ isWorkspaceLink: boolean;
48
+ } {
49
+ const packagePath = join(cwd, "node_modules", PACKAGE_NAME, "package.json");
50
+ const linkPath = join(cwd, "node_modules", PACKAGE_NAME);
51
+
52
+ let isWorkspaceLink = false;
53
+
54
+ try {
55
+ isWorkspaceLink = lstatSync(linkPath).isSymbolicLink();
56
+ } catch {
57
+ try {
58
+ const hostPkg = JSON.parse(
59
+ readFileSync(join(cwd, "package.json"), "utf8"),
60
+ ) as {
61
+ dependencies?: Record<string, string>;
62
+ devDependencies?: Record<string, string>;
63
+ };
64
+ const depSpec =
65
+ hostPkg.dependencies?.[PACKAGE_NAME] ??
66
+ hostPkg.devDependencies?.[PACKAGE_NAME];
67
+ isWorkspaceLink = depSpec?.startsWith("workspace:") ?? false;
68
+ } catch {
69
+ // Ignore — treat as non-workspace.
70
+ }
71
+ }
72
+
73
+ try {
74
+ const pkg = JSON.parse(readFileSync(packagePath, "utf8")) as { version?: string };
75
+ return { installed: pkg.version ?? null, isWorkspaceLink };
76
+ } catch {
77
+ return { installed: null, isWorkspaceLink };
78
+ }
79
+ }
80
+
81
+ async function fetchLatestFromNpm(): Promise<{
82
+ version: string | null;
83
+ repositoryUrl?: string;
84
+ }> {
85
+ const now = Date.now();
86
+
87
+ if (cachedLatest && now - cachedLatest.fetchedAt < CACHE_TTL_MS) {
88
+ return {
89
+ version: cachedLatest.version,
90
+ repositoryUrl: cachedLatest.repositoryUrl,
91
+ };
92
+ }
93
+
94
+ try {
95
+ const response = await fetch(NPM_LATEST_URL, {
96
+ next: { revalidate: 3600 },
97
+ });
98
+
99
+ if (!response.ok) {
100
+ return { version: null };
101
+ }
102
+
103
+ const data = (await response.json()) as NpmLatestResponse;
104
+ const version = data.version ?? null;
105
+ const repositoryUrl = data.repository?.url?.replace(/^git\+/, "").replace(/\.git$/, "");
106
+
107
+ if (version) {
108
+ cachedLatest = { version, repositoryUrl, fetchedAt: now };
109
+ }
110
+
111
+ return { version, repositoryUrl };
112
+ } catch {
113
+ return { version: null };
114
+ }
115
+ }
116
+
117
+ export async function GET() {
118
+ const cwd = process.cwd();
119
+ const { installed, isWorkspaceLink } = readInstalledVersion(cwd);
120
+
121
+ if (isWorkspaceLink) {
122
+ const status: ProtoPluginVersionStatus = {
123
+ installed,
124
+ latest: null,
125
+ updateAvailable: false,
126
+ isWorkspaceLink: true,
127
+ };
128
+ return NextResponse.json(status);
129
+ }
130
+
131
+ const { version: latest, repositoryUrl } = await fetchLatestFromNpm();
132
+
133
+ const updateAvailable =
134
+ installed != null && latest != null && isNewerVersion(latest, installed);
135
+
136
+ const status: ProtoPluginVersionStatus = {
137
+ installed,
138
+ latest,
139
+ updateAvailable,
140
+ isWorkspaceLink: false,
141
+ repositoryUrl,
142
+ };
143
+
144
+ return NextResponse.json(status);
145
+ }
package/src/server.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import "server-only";
2
2
 
3
+ export { createPrototypeGalleryPage } from "./config/create-prototype-gallery-page";
4
+
3
5
  export {
4
6
  readScreenshotManifest,
5
7
  touchScreenshotManifest,
@@ -40,17 +40,17 @@
40
40
  Chrome (ground) is darkest for sidebar + header; content (bg) is a lighter
41
41
  elevated panel for the gallery grid and review preview frame.
42
42
 
43
- Neutral scale (measured against the #1a1a1a content surface):
43
+ Neutral scale (measured against the #151515 content surface):
44
44
  heading #f0f0f0 ~13:1 crisp titles
45
45
  text #d0d0d0 ~9:1 body copy
46
46
  muted #a0a0a0 ~5:1 labels, descriptions (AA)
47
47
  icon #9a9a9a glyphs, dimmer than body
48
48
  border #333333 hairline dividers
49
49
  strong #454545 inputs, structural edges */
50
- --tool-chrome-ground: #0f0f0f;
51
- --tool-chrome-bg: #1a1a1a;
52
- --tool-chrome-surface: #202020;
53
- --tool-chrome-surface-muted: #161616;
50
+ --tool-chrome-ground: #121212;
51
+ --tool-chrome-bg: #151515;
52
+ --tool-chrome-surface: #1c1c1c;
53
+ --tool-chrome-surface-muted: #181818;
54
54
  --tool-chrome-text: #d0d0d0;
55
55
  --tool-chrome-text-heading: #f0f0f0;
56
56
  --tool-chrome-text-muted: #9c9c9c;
@@ -83,7 +83,7 @@
83
83
  --bg-main: var(--tool-chrome-bg);
84
84
  --bg-top: var(--tool-chrome-surface);
85
85
  --bg-subtle: var(--tool-chrome-surface-muted);
86
- --bg-layered: #202020;
86
+ --bg-layered: #1c1c1c;
87
87
  --bg-invert: var(--tool-chrome-text-heading);
88
88
  --bg-white: var(--tool-chrome-bg);
89
89
 
@@ -165,7 +165,7 @@
165
165
  --color-bg-main: var(--tool-chrome-bg);
166
166
  --color-bg-top: var(--tool-chrome-surface);
167
167
  --color-bg-subtle: var(--tool-chrome-surface-muted);
168
- --color-bg-layered: #202020;
168
+ --color-bg-layered: #1c1c1c;
169
169
  --color-bg-invert: var(--tool-chrome-text-heading);
170
170
  --color-bg-white: var(--tool-chrome-bg);
171
171
  --color-text-primary: var(--tool-chrome-text);
@@ -209,7 +209,7 @@
209
209
  --bg-main: var(--tool-chrome-bg);
210
210
  --bg-top: var(--tool-chrome-bg);
211
211
  --bg-subtle: var(--tool-chrome-surface-muted);
212
- --bg-layered: #242424;
212
+ --bg-layered: #1a1a1a;
213
213
  --border-faint: var(--tool-chrome-border);
214
214
  --border-solid: var(--tool-chrome-border);
215
215
  --border-medium: var(--tool-chrome-border-strong);
@@ -241,7 +241,7 @@
241
241
  --color-bg-main: var(--tool-chrome-bg);
242
242
  --color-bg-top: var(--tool-chrome-bg);
243
243
  --color-bg-subtle: var(--tool-chrome-surface-muted);
244
- --color-bg-layered: #242424;
244
+ --color-bg-layered: #1a1a1a;
245
245
  --color-text-primary: var(--tool-chrome-text-heading);
246
246
  --color-text-secondary: var(--tool-chrome-text);
247
247
  --color-text-tertiary: var(--tool-chrome-text-muted);
@@ -490,7 +490,7 @@ html.prototype-theme-transition-disable :is(*, *::before, *::after) {
490
490
 
491
491
  @media (hover: hover) and (pointer: fine) {
492
492
  [data-prototype-root] .tool-toolbar-button:hover {
493
- background: #202020;
493
+ background: var(--tool-chrome-surface);
494
494
  color: var(--tool-chrome-icon-hover);
495
495
  }
496
496
  }
@@ -728,7 +728,7 @@ html.prototype-theme-transition-disable :is(*, *::before, *::after) {
728
728
 
729
729
  /* State map chrome — dedicated canvas bed + node footer tokens per theme. */
730
730
  [data-prototype-root] [data-prototype-state-canvas] {
731
- --prototype-state-map-canvas-bg: var(--tool-chrome-bg);
731
+ --prototype-state-map-canvas-bg: var(--tool-chrome-ground);
732
732
  --prototype-state-map-node-surface: var(--tool-chrome-surface);
733
733
  --prototype-state-map-node-preview-bg: var(--tool-chrome-surface-muted);
734
734
  --prototype-state-map-node-footer-bg: var(--tool-chrome-surface-muted);
@@ -740,12 +740,12 @@ html.prototype-theme-transition-disable :is(*, *::before, *::after) {
740
740
  }
741
741
 
742
742
  [data-prototype-root][data-prototype-comment-theme="dark"] [data-prototype-state-canvas] {
743
- --prototype-state-map-canvas-bg: #282828;
744
- --prototype-state-map-node-surface: #343434;
745
- --prototype-state-map-node-preview-bg: #303030;
746
- --prototype-state-map-node-footer-bg: #404040;
747
- --prototype-state-map-node-footer-border: #525252;
748
- --prototype-state-map-node-footer-text: #ffffff;
743
+ --prototype-state-map-canvas-bg: var(--tool-chrome-ground);
744
+ --prototype-state-map-node-surface: var(--tool-chrome-surface);
745
+ --prototype-state-map-node-preview-bg: var(--tool-chrome-surface-muted);
746
+ --prototype-state-map-node-footer-bg: var(--tool-chrome-surface-muted);
747
+ --prototype-state-map-node-footer-border: var(--tool-chrome-border-strong);
748
+ --prototype-state-map-node-footer-text: var(--tool-chrome-text-heading);
749
749
  }
750
750
 
751
751
  /* Restore host prototype preview theme inside screenshot capture and source