proto-plugin 0.1.0

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 (209) hide show
  1. package/AGENTS.md +350 -0
  2. package/README.md +279 -0
  3. package/components.json +18 -0
  4. package/package.json +73 -0
  5. package/scripts/download-mobbin-reference-images.mjs +112 -0
  6. package/scripts/lib/host-config.example.sh +48 -0
  7. package/scripts/lib/host-config.sh +25 -0
  8. package/scripts/lib/resolve-host-root.mjs +18 -0
  9. package/scripts/lib/source-path.sh +49 -0
  10. package/scripts/link-source-db.sh +128 -0
  11. package/scripts/link-source.sh +40 -0
  12. package/scripts/publish-package.sh +147 -0
  13. package/scripts/share-prototype.sh +279 -0
  14. package/scripts/sync-from-source.sh +74 -0
  15. package/scripts/verify-prototype-component-ids.mjs +151 -0
  16. package/scripts/verify-prototype-preview-states.mjs +135 -0
  17. package/src/app.ts +4 -0
  18. package/src/components/platform-ui/code-block.tsx +21 -0
  19. package/src/components/platform-ui/controls-panel-options.tsx +71 -0
  20. package/src/components/platform-ui/controls-panel-select.tsx +438 -0
  21. package/src/components/platform-ui/empty-state.tsx +15 -0
  22. package/src/components/platform-ui/icon-button.tsx +18 -0
  23. package/src/components/platform-ui/panel-select.tsx +123 -0
  24. package/src/components/platform-ui/panel.tsx +97 -0
  25. package/src/components/platform-ui/review-dropdown-menu.tsx +35 -0
  26. package/src/components/platform-ui/sidebar.tsx +293 -0
  27. package/src/components/platform-ui/toolbar-icon-button.tsx +34 -0
  28. package/src/components/platform-ui/toolbar.tsx +37 -0
  29. package/src/components/prototype-gallery-client.tsx +57 -0
  30. package/src/components/prototype-provider.tsx +20 -0
  31. package/src/components/prototypes/mini-pill-label.tsx +37 -0
  32. package/src/components/prototypes/prototype-change-log-panel.tsx +207 -0
  33. package/src/components/prototypes/prototype-comment-provider.tsx +61 -0
  34. package/src/components/prototypes/prototype-comment-toolbar.tsx +65 -0
  35. package/src/components/prototypes/prototype-control.tsx +31 -0
  36. package/src/components/prototypes/prototype-controls.tsx +553 -0
  37. package/src/components/prototypes/prototype-create-exploration-modal.tsx +117 -0
  38. package/src/components/prototypes/prototype-create-state-modal.tsx +117 -0
  39. package/src/components/prototypes/prototype-default-state-map-page.tsx +66 -0
  40. package/src/components/prototypes/prototype-design-brief-modal.tsx +128 -0
  41. package/src/components/prototypes/prototype-design-brief-panel.tsx +525 -0
  42. package/src/components/prototypes/prototype-design-context-panel.tsx +65 -0
  43. package/src/components/prototypes/prototype-floating-pill.module.scss +1341 -0
  44. package/src/components/prototypes/prototype-mobbin-gallery.tsx +330 -0
  45. package/src/components/prototypes/prototype-reference-docs.tsx +222 -0
  46. package/src/components/prototypes/prototype-review-chrome.module.scss +630 -0
  47. package/src/components/prototypes/prototype-review-chrome.tsx +1241 -0
  48. package/src/components/prototypes/prototype-review-dropdown-menu-item.tsx +1 -0
  49. package/src/components/prototypes/prototype-review-dropdown-menu-separator.tsx +1 -0
  50. package/src/components/prototypes/prototype-review-panel-menu-item.tsx +1 -0
  51. package/src/components/prototypes/prototype-review-panel-menu-separator.tsx +1 -0
  52. package/src/components/prototypes/prototype-review-panel-select.tsx +4 -0
  53. package/src/components/prototypes/prototype-review-sidebar.tsx +167 -0
  54. package/src/components/prototypes/prototype-share-link-restore.tsx +75 -0
  55. package/src/components/prototypes/prototype-share-mode.tsx +206 -0
  56. package/src/components/prototypes/prototype-spec-panel-content.tsx +538 -0
  57. package/src/components/prototypes/prototype-state-canvas-overlay.module.scss +336 -0
  58. package/src/components/prototypes/prototype-state-canvas-overlay.tsx +1023 -0
  59. package/src/components/prototypes/prototype-state-canvas.tsx +38 -0
  60. package/src/components/prototypes/prototype-state-screenshot-preview.tsx +47 -0
  61. package/src/components/prototypes/prototype-target.tsx +120 -0
  62. package/src/components/prototypes/prototype-tweak-highlight.tsx +132 -0
  63. package/src/components/prototypes/prototype-tweaks.tsx +48 -0
  64. package/src/components/prototypes/prototype-variant-explorer.tsx +347 -0
  65. package/src/components/prototypes/prototype-variant-rationale-panel.tsx +49 -0
  66. package/src/components/prototypes/prototype-variant-tabs.tsx +207 -0
  67. package/src/components/prototypes/prototype-vercel-preview-modal.tsx +120 -0
  68. package/src/components/prototypes/state-map-annotation-tooltip.module.scss +89 -0
  69. package/src/components/prototypes/state-map-annotation-tooltip.tsx +137 -0
  70. package/src/components/prototypes/state-map-wireframe-highlight.module.scss +25 -0
  71. package/src/components/prototypes/state-map-wireframe-highlight.tsx +82 -0
  72. package/src/components/shell/prototype-gallery-shell.tsx +79 -0
  73. package/src/components/shell/prototype-screenshot-capture.tsx +45 -0
  74. package/src/components/shell/prototype-shell.module.scss +194 -0
  75. package/src/components/shell/prototype-shell.tsx +160 -0
  76. package/src/components/shell/prototype-state-screenshot-capture.tsx +68 -0
  77. package/src/components/ui/button.tsx +58 -0
  78. package/src/components/ui/dialog.module.scss +81 -0
  79. package/src/components/ui/dialog.tsx +127 -0
  80. package/src/components/ui/dropdown-menu.tsx +200 -0
  81. package/src/components/ui/input.tsx +22 -0
  82. package/src/components/ui/label.tsx +26 -0
  83. package/src/components/ui/option-select.tsx +81 -0
  84. package/src/components/ui/scroll-area.tsx +48 -0
  85. package/src/components/ui/separator.tsx +31 -0
  86. package/src/components/ui/slider.tsx +28 -0
  87. package/src/components/ui/switch.tsx +29 -0
  88. package/src/components/ui/tabs.tsx +55 -0
  89. package/src/components/ui/toggle-group.tsx +80 -0
  90. package/src/components/ui/tooltip.tsx +32 -0
  91. package/src/config/create-prototype-gallery-page.tsx +65 -0
  92. package/src/config/create-prototype-host-app.tsx +121 -0
  93. package/src/config/create-prototype-page.tsx +41 -0
  94. package/src/config/create-prototype-site-layout.tsx +28 -0
  95. package/src/config/define-prototype-config.ts +7 -0
  96. package/src/config/prototype-icon.tsx +25 -0
  97. package/src/config/prototype-site-layout-client.tsx +35 -0
  98. package/src/config/with-prototype.ts +20 -0
  99. package/src/index.ts +303 -0
  100. package/src/lib/pr-split/build-pr-agent-prompt.ts +68 -0
  101. package/src/lib/pr-split/build-pr-split-prototype-url.ts +12 -0
  102. package/src/lib/pr-split/pr-split-highlight.ts +6 -0
  103. package/src/lib/pr-split/pr-split-types.ts +58 -0
  104. package/src/lib/pr-split/resolve-source-preview-url.ts +45 -0
  105. package/src/lib/pr-split/use-pr-vercel-previews.ts +69 -0
  106. package/src/lib/prototype-comments/core/annotation-channel.ts +41 -0
  107. package/src/lib/prototype-comments/core/annotation-status.ts +60 -0
  108. package/src/lib/prototype-comments/core/annotation-target.ts +567 -0
  109. package/src/lib/prototype-comments/core/capture-theme.ts +41 -0
  110. package/src/lib/prototype-comments/core/capture.ts +11 -0
  111. package/src/lib/prototype-comments/core/comment-capture-blocked.ts +47 -0
  112. package/src/lib/prototype-comments/core/comment-threads.ts +52 -0
  113. package/src/lib/prototype-comments/core/element-identification.ts +615 -0
  114. package/src/lib/prototype-comments/core/export.ts +35 -0
  115. package/src/lib/prototype-comments/core/freeze-animations.ts +266 -0
  116. package/src/lib/prototype-comments/core/identify-element.ts +76 -0
  117. package/src/lib/prototype-comments/core/normalize-viewport.ts +35 -0
  118. package/src/lib/prototype-comments/core/react-detection.ts +704 -0
  119. package/src/lib/prototype-comments/core/restore.ts +7 -0
  120. package/src/lib/prototype-comments/core/screenshot.ts +428 -0
  121. package/src/lib/prototype-comments/core/source-location.ts +906 -0
  122. package/src/lib/prototype-comments/core/storage.ts +80 -0
  123. package/src/lib/prototype-comments/core/types.ts +79 -0
  124. package/src/lib/prototype-comments/core/validation.ts +17 -0
  125. package/src/lib/prototype-comments/hooks/useLayoutShift.ts +61 -0
  126. package/src/lib/prototype-comments/index.ts +58 -0
  127. package/src/lib/prototype-comments/react/CommentCaptureToolbar.tsx +1398 -0
  128. package/src/lib/prototype-comments/react/CommentProvider.tsx +298 -0
  129. package/src/lib/prototype-comments/scss.d.ts +10 -0
  130. package/src/lib/prototype-comments/ui/CommentAnnotationOverlay.tsx +253 -0
  131. package/src/lib/prototype-comments/ui/CommentDetailPage.tsx +56 -0
  132. package/src/lib/prototype-comments/ui/CommentHeaderCount.tsx +28 -0
  133. package/src/lib/prototype-comments/ui/CommentThread.tsx +331 -0
  134. package/src/lib/prototype-comments/ui/CommentsGrid.tsx +780 -0
  135. package/src/lib/prototype-comments/ui/CommentsListPage.tsx +25 -0
  136. package/src/lib/prototype-comments/ui/CommentsSidebar.tsx +77 -0
  137. package/src/lib/prototype-comments/ui/annotation-marker/index.tsx +200 -0
  138. package/src/lib/prototype-comments/ui/annotation-marker/styles.module.scss +194 -0
  139. package/src/lib/prototype-comments/ui/annotation-popup/index.tsx +335 -0
  140. package/src/lib/prototype-comments/ui/annotation-popup/styles.module.scss +469 -0
  141. package/src/lib/prototype-comments/ui/capture-styles.module.scss +2225 -0
  142. package/src/lib/prototype-comments/ui/comment-highlight.ts +48 -0
  143. package/src/lib/prototype-comments/ui/comment-thread.module.scss +203 -0
  144. package/src/lib/prototype-comments/ui/comments-grid-card.module.scss +253 -0
  145. package/src/lib/prototype-comments/ui/comments-sidebar.module.scss +123 -0
  146. package/src/lib/prototype-comments/ui/icon-transitions.module.scss +51 -0
  147. package/src/lib/prototype-comments/ui/icons.tsx +1086 -0
  148. package/src/lib/prototype-comments/ui/is-comment-detail-path.ts +7 -0
  149. package/src/lib/prototypes/capture-prototype-viewport.ts +32 -0
  150. package/src/lib/prototypes/changelog-meta-context.tsx +162 -0
  151. package/src/lib/prototypes/changelog-meta-storage.ts +62 -0
  152. package/src/lib/prototypes/changelog-meta.ts +95 -0
  153. package/src/lib/prototypes/create-prototype-registry.ts +74 -0
  154. package/src/lib/prototypes/create-state-screenshot-thumbnail.ts +46 -0
  155. package/src/lib/prototypes/design-exploration-default-storage.ts +68 -0
  156. package/src/lib/prototypes/design-exploration-default.ts +51 -0
  157. package/src/lib/prototypes/design-exploration-types.ts +203 -0
  158. package/src/lib/prototypes/prototype-comment-registry.tsx +145 -0
  159. package/src/lib/prototypes/prototype-comment-review-bridge.tsx +40 -0
  160. package/src/lib/prototypes/prototype-comment-review-state.ts +57 -0
  161. package/src/lib/prototypes/prototype-component-registry.ts +40 -0
  162. package/src/lib/prototypes/prototype-config-types.ts +34 -0
  163. package/src/lib/prototypes/prototype-preview-state-registry.ts +443 -0
  164. package/src/lib/prototypes/prototype-preview-state-types.ts +89 -0
  165. package/src/lib/prototypes/prototype-review-context.tsx +905 -0
  166. package/src/lib/prototypes/prototype-review-url.ts +134 -0
  167. package/src/lib/prototypes/prototype-share-link.ts +175 -0
  168. package/src/lib/prototypes/prototype-state-canvas-constants.ts +118 -0
  169. package/src/lib/prototypes/prototype-state-canvas-types.ts +159 -0
  170. package/src/lib/prototypes/prototype-state-screenshot.ts +39 -0
  171. package/src/lib/prototypes/prototype-tweak-registry.ts +96 -0
  172. package/src/lib/prototypes/prototype-tweak-types.ts +31 -0
  173. package/src/lib/prototypes/reference-docs.ts +137 -0
  174. package/src/lib/prototypes/screenshot-manifest.ts +33 -0
  175. package/src/lib/prototypes/screenshot-src.ts +7 -0
  176. package/src/lib/prototypes/share-command.ts +8 -0
  177. package/src/lib/prototypes/state-map-annotation.ts +70 -0
  178. package/src/lib/prototypes/use-design-exploration-default.ts +114 -0
  179. package/src/lib/prototypes/use-persisted-local-state.ts +140 -0
  180. package/src/lib/prototypes/use-prototype-comments.ts +63 -0
  181. package/src/lib/prototypes/use-prototype-mobile-viewport-frame.ts +46 -0
  182. package/src/lib/prototypes/use-prototype-state-screenshot-src.ts +71 -0
  183. package/src/lib/prototypes/use-prototype-tool-theme.tsx +115 -0
  184. package/src/lib/prototypes/use-prototype-viewport-frame.ts +34 -0
  185. package/src/lib/prototypes/use-register-prototype-reference-docs.ts +16 -0
  186. package/src/lib/prototypes/use-snap-corner-position.ts +417 -0
  187. package/src/lib/prototypes/use-toolbar-hover-panel.ts +98 -0
  188. package/src/lib/prototypes/variant-set-lucide-icon.tsx +55 -0
  189. package/src/lib/tool-portal.ts +33 -0
  190. package/src/lib/use-copy-to-clipboard.ts +81 -0
  191. package/src/lib/utils.ts +6 -0
  192. package/src/lib/vercel-preview/parse-vercel-github-comment.ts +81 -0
  193. package/src/server/create-host-api-route.ts +100 -0
  194. package/src/server/create-prototype-api-routes.ts +428 -0
  195. package/src/server/gallery/create-gallery-api.ts +56 -0
  196. package/src/server/gallery/folders-route.ts +156 -0
  197. package/src/server/gallery/gallery-env.ts +39 -0
  198. package/src/server/gallery/gallery-route.ts +150 -0
  199. package/src/server/gallery/gallery-types.ts +15 -0
  200. package/src/server/gallery/image-route.ts +59 -0
  201. package/src/server/gallery/item-route.ts +92 -0
  202. package/src/server/pr-vercel-preview-route.ts +162 -0
  203. package/src/server/redis/client.ts +55 -0
  204. package/src/server/redis/prototype-changelog-meta.ts +31 -0
  205. package/src/server/redis/prototype-comments.ts +92 -0
  206. package/src/server/redis/prototype-design-exploration-default.ts +32 -0
  207. package/src/server.ts +51 -0
  208. package/src/styles/globals.css +624 -0
  209. package/tsconfig.json +19 -0
@@ -0,0 +1,279 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ TOOL_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
5
+ # shellcheck source=lib/source-path.sh
6
+ source "$TOOL_ROOT/scripts/lib/source-path.sh"
7
+ HOST_ROOT="$(resolve_host_root "$TOOL_ROOT")"
8
+ cd "$HOST_ROOT"
9
+
10
+ if [[ -z "${VERCEL_PROJECT:-}" && -f "$HOST_ROOT/package.json" ]]; then
11
+ VERCEL_PROJECT="$(node -pe "require('$HOST_ROOT/package.json').name" 2>/dev/null || true)"
12
+ fi
13
+ VERCEL_PROJECT="${VERCEL_PROJECT:-}"
14
+ PUSH=1
15
+ OPEN=0
16
+ SLUG=""
17
+
18
+ usage() {
19
+ cat <<EOF
20
+ Usage: $(basename "$0") [options] <prototype-slug>
21
+
22
+ Push the current branch, wait for the Vercel preview deployment, and copy a
23
+ shareable link to the prototype (e.g. /prototypes/example-feature).
24
+
25
+ Before pushing, stages all changes (git add -A) and commits them when needed.
26
+
27
+ Options:
28
+ --no-push Skip git push (use the current branch HEAD on GitHub)
29
+ --open Open the shareable link in your browser
30
+ --project <name> Vercel project to use (default: from VERCEL_PROJECT or package.json name)
31
+ -h, --help Show this help message
32
+
33
+ Examples:
34
+ pnpm share-prototype example-feature
35
+ pnpm share-prototype --no-push example-feature
36
+
37
+ Requires: git, gh (authenticated), and network access.
38
+ EOF
39
+ }
40
+
41
+ while [[ $# -gt 0 ]]; do
42
+ case "$1" in
43
+ --no-push)
44
+ PUSH=0
45
+ shift
46
+ ;;
47
+ --open)
48
+ OPEN=1
49
+ shift
50
+ ;;
51
+ --project)
52
+ [[ $# -ge 2 ]] || {
53
+ echo "Missing value for --project" >&2
54
+ exit 1
55
+ }
56
+ VERCEL_PROJECT="$2"
57
+ shift 2
58
+ ;;
59
+ -h | --help)
60
+ usage
61
+ exit 0
62
+ ;;
63
+ -*)
64
+ echo "Unknown option: $1" >&2
65
+ usage >&2
66
+ exit 1
67
+ ;;
68
+ *)
69
+ if [[ -n "$SLUG" ]]; then
70
+ echo "Unexpected argument: $1" >&2
71
+ usage >&2
72
+ exit 1
73
+ fi
74
+ SLUG="$1"
75
+ shift
76
+ ;;
77
+ esac
78
+ done
79
+
80
+ if [[ -z "$SLUG" ]]; then
81
+ usage >&2
82
+ exit 1
83
+ fi
84
+
85
+ if [[ -z "$VERCEL_PROJECT" ]]; then
86
+ echo "Set VERCEL_PROJECT or pass --project <name>." >&2
87
+ exit 1
88
+ fi
89
+
90
+ require_command() {
91
+ if ! command -v "$1" >/dev/null 2>&1; then
92
+ echo "Missing required command: $1" >&2
93
+ exit 1
94
+ fi
95
+ }
96
+
97
+ require_command git
98
+ require_command gh
99
+
100
+ if ! gh auth status >/dev/null 2>&1; then
101
+ echo "gh is not authenticated. Run: gh auth login" >&2
102
+ exit 1
103
+ fi
104
+
105
+ remote_url="$(git remote get-url origin 2>/dev/null || true)"
106
+ if [[ -z "$remote_url" ]]; then
107
+ echo "No git remote named origin found." >&2
108
+ exit 1
109
+ fi
110
+
111
+ if [[ "$remote_url" =~ github\.com[:/]([^/]+)/([^/.]+)(\.git)?$ ]]; then
112
+ REPO_OWNER="${BASH_REMATCH[1]}"
113
+ REPO_NAME="${BASH_REMATCH[2]%.git}"
114
+ else
115
+ echo "Could not parse GitHub owner/repo from origin: $remote_url" >&2
116
+ exit 1
117
+ fi
118
+
119
+ branch="$(git branch --show-current)"
120
+ if [[ -z "$branch" ]]; then
121
+ echo "Detached HEAD — checkout a branch before sharing." >&2
122
+ exit 1
123
+ fi
124
+
125
+ copy_to_clipboard() {
126
+ local text="$1"
127
+ if command -v pbcopy >/dev/null 2>&1; then
128
+ printf '%s' "$text" | pbcopy
129
+ elif command -v wl-copy >/dev/null 2>&1; then
130
+ printf '%s' "$text" | wl-copy
131
+ elif command -v xclip >/dev/null 2>&1; then
132
+ printf '%s' "$text" | xclip -selection clipboard
133
+ else
134
+ echo "Could not copy to clipboard (install pbcopy, wl-copy, or xclip)." >&2
135
+ return 1
136
+ fi
137
+ }
138
+
139
+ extract_branch_preview_host() {
140
+ local sha="$1"
141
+ local summary
142
+
143
+ summary="$(
144
+ gh api \
145
+ "repos/$REPO_OWNER/$REPO_NAME/commits/$sha/check-runs?per_page=30" \
146
+ --jq '.check_runs[] | select(.name == "Vercel Preview Comments") | .output.summary' \
147
+ 2>/dev/null | head -1
148
+ )"
149
+
150
+ if [[ -z "$summary" ]]; then
151
+ return 1
152
+ fi
153
+
154
+ if [[ "$summary" =~ vercel\.live/open-feedback/([a-z0-9.-]+\.vercel\.app) ]]; then
155
+ printf '%s\n' "${BASH_REMATCH[1]}"
156
+ return 0
157
+ fi
158
+
159
+ if [[ "$summary" =~ (https?://)?([a-z0-9.-]+\.vercel\.app) ]]; then
160
+ printf '%s\n' "${BASH_REMATCH[2]}"
161
+ return 0
162
+ fi
163
+
164
+ return 1
165
+ }
166
+
167
+ extract_deployment_preview_url() {
168
+ local sha="$1"
169
+ local deployment_id environment_url
170
+
171
+ deployment_id="$(
172
+ gh api \
173
+ "repos/$REPO_OWNER/$REPO_NAME/deployments?sha=$sha&per_page=20" \
174
+ --jq ".[] | select(.environment == \"Preview – $VERCEL_PROJECT\") | .id" \
175
+ 2>/dev/null | head -1
176
+ )"
177
+
178
+ if [[ -z "$deployment_id" ]]; then
179
+ return 1
180
+ fi
181
+
182
+ environment_url="$(
183
+ gh api \
184
+ "repos/$REPO_OWNER/$REPO_NAME/deployments/$deployment_id/statuses" \
185
+ --jq '.[] | select(.state == "success" and .environment_url != null) | .environment_url' \
186
+ 2>/dev/null | head -1
187
+ )"
188
+
189
+ if [[ -z "$environment_url" ]]; then
190
+ return 1
191
+ fi
192
+
193
+ printf '%s\n' "$environment_url"
194
+ }
195
+
196
+ resolve_preview_origin() {
197
+ local sha="$1"
198
+ local host origin
199
+
200
+ if host="$(extract_branch_preview_host "$sha")"; then
201
+ printf 'https://%s\n' "$host"
202
+ return 0
203
+ fi
204
+
205
+ if origin="$(extract_deployment_preview_url "$sha")"; then
206
+ origin="${origin%/}"
207
+ printf '%s\n' "$origin"
208
+ return 0
209
+ fi
210
+
211
+ return 1
212
+ }
213
+
214
+ wait_for_preview_origin() {
215
+ local sha="$1"
216
+ local attempt=0
217
+ local max_attempts=60
218
+ local origin=""
219
+
220
+ while (( attempt < max_attempts )); do
221
+ if origin="$(resolve_preview_origin "$sha")"; then
222
+ printf '%s\n' "$origin"
223
+ return 0
224
+ fi
225
+
226
+ sleep 10
227
+ ((attempt += 1))
228
+ done
229
+
230
+ echo "Timed out waiting for Vercel preview deployment." >&2
231
+ echo "Check deployments: https://github.com/$REPO_OWNER/$REPO_NAME/deployments" >&2
232
+ return 1
233
+ }
234
+
235
+ share_url=""
236
+ sha=""
237
+
238
+ if [[ "$PUSH" -eq 1 ]]; then
239
+ echo "Staging changes..."
240
+ git add -A
241
+
242
+ if ! git diff --cached --quiet; then
243
+ echo "Committing staged changes..."
244
+ git commit -m "Update prototype: $SLUG"
245
+ fi
246
+
247
+ echo "Pushing branch: $branch"
248
+ git push -u origin HEAD
249
+ else
250
+ echo "Skipping push (using current branch HEAD on origin)."
251
+ fi
252
+
253
+ sha="$(git rev-parse HEAD)"
254
+ echo "Commit: ${sha:0:7} on $branch"
255
+ echo "Building..." >&2
256
+
257
+ origin="$(wait_for_preview_origin "$sha")"
258
+ share_url="${origin%/}/prototypes/${SLUG}"
259
+
260
+ echo
261
+ echo "Shareable link:"
262
+ echo "$share_url"
263
+ echo
264
+
265
+ if copy_to_clipboard "$share_url"; then
266
+ echo "Copied to clipboard."
267
+ else
268
+ echo "Copy the link above manually."
269
+ fi
270
+
271
+ if [[ "$OPEN" -eq 1 ]]; then
272
+ if command -v open >/dev/null 2>&1; then
273
+ open "$share_url"
274
+ elif command -v xdg-open >/dev/null 2>&1; then
275
+ xdg-open "$share_url"
276
+ else
277
+ echo "Could not open browser automatically." >&2
278
+ fi
279
+ fi
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ TOOL_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
5
+ # shellcheck source=lib/source-path.sh
6
+ source "$TOOL_ROOT/scripts/lib/source-path.sh"
7
+ # shellcheck source=lib/host-config.sh
8
+ source "$TOOL_ROOT/scripts/lib/host-config.sh"
9
+ HOST_ROOT="$(resolve_host_root "$TOOL_ROOT")"
10
+
11
+ if ! load_host_config "$HOST_ROOT"; then
12
+ exit 1
13
+ fi
14
+
15
+ if ! SOURCE="$(resolve_source_path "$HOST_ROOT")"; then
16
+ echo "Source app path not found." >&2
17
+ echo "Set SOURCE_PATH in .env.local (see .env.example), run pnpm link-source, or pass SOURCE=/path/to/source-app" >&2
18
+ exit 1
19
+ fi
20
+
21
+ echo "Syncing from $SOURCE"
22
+
23
+ copy_file() {
24
+ local rel="$1"
25
+ mkdir -p "$HOST_ROOT/$(dirname "$rel")"
26
+ cp "$SOURCE/$rel" "$HOST_ROOT/$rel"
27
+ }
28
+
29
+ copy_dir() {
30
+ local rel="$1"
31
+ mkdir -p "$HOST_ROOT/$rel"
32
+ cp -R "$SOURCE/$rel/." "$HOST_ROOT/$rel/"
33
+ }
34
+
35
+ copy_glob() {
36
+ local glob="$1" dest matches
37
+ dest="$(dirname "$glob")"
38
+ mkdir -p "$HOST_ROOT/$dest"
39
+ # shellcheck disable=SC2206
40
+ matches=( "$SOURCE"/$glob )
41
+ if [[ -e "${matches[0]}" ]]; then
42
+ cp "${matches[@]}" "$HOST_ROOT/$dest/"
43
+ fi
44
+ }
45
+
46
+ for rel in ${SYNC_FILES[@]+"${SYNC_FILES[@]}"}; do
47
+ copy_file "$rel"
48
+ done
49
+ for rel in ${SYNC_DIRS[@]+"${SYNC_DIRS[@]}"}; do
50
+ copy_dir "$rel"
51
+ done
52
+ for glob in ${SYNC_GLOBS[@]+"${SYNC_GLOBS[@]}"}; do
53
+ copy_glob "$glob"
54
+ done
55
+
56
+ # Re-apply platform Tailwind source scan (package self-reference; not in source app).
57
+ if ! grep -q '@source "../../packages/prototype' "$HOST_ROOT/src/app/globals.css"; then
58
+ if [[ "$OSTYPE" == darwin* ]]; then
59
+ sed -i '' '/^@import "tailwindcss";/a\
60
+ @source "../../packages/prototype/src/**/*.{js,ts,jsx,tsx}";
61
+ ' "$HOST_ROOT/src/app/globals.css"
62
+ else
63
+ sed -i '/^@import "tailwindcss";/a @source "../../packages/prototype/src/**/*.{js,ts,jsx,tsx}";' "$HOST_ROOT/src/app/globals.css"
64
+ fi
65
+ fi
66
+
67
+ if [[ "${SYNC_LOCAL_EXTENSIONS+set}" == set && "${#SYNC_LOCAL_EXTENSIONS[@]}" -gt 0 ]]; then
68
+ echo "Done. Re-apply local extensions:"
69
+ for ext in "${SYNC_LOCAL_EXTENSIONS[@]}"; do
70
+ echo " - $ext"
71
+ done
72
+ else
73
+ echo "Done."
74
+ fi
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Ensures every UI component under src/prototypes/{slug}/ is registered in
4
+ * component-ids.ts and wraps its root JSX with PrototypeTarget/PrototypeComponent/PrototypeControl.
5
+ */
6
+ import fs from "node:fs";
7
+ import path from "node:path";
8
+ import {
9
+ resolveHostRoot,
10
+ resolveToolRoot,
11
+ } from "./lib/resolve-host-root.mjs";
12
+
13
+ const toolRoot = resolveToolRoot(import.meta.url);
14
+ const repoRoot = resolveHostRoot(toolRoot);
15
+ const prototypesDir = path.join(repoRoot, "src/prototypes");
16
+
17
+ function kebabCase(name) {
18
+ return name
19
+ .replace(/([a-z0-9])([A-Z])/g, "$1-$2")
20
+ .replace(/_/g, "-")
21
+ .toLowerCase();
22
+ }
23
+
24
+ function listPrototypeSlugs() {
25
+ return fs
26
+ .readdirSync(prototypesDir, { withFileTypes: true })
27
+ .filter((d) => d.isDirectory() && !d.name.startsWith("."))
28
+ .map((d) => d.name);
29
+ }
30
+
31
+ function readRegistryIds(slug) {
32
+ const registryPath = path.join(prototypesDir, slug, "component-ids.ts");
33
+ if (!fs.existsSync(registryPath)) {
34
+ return null;
35
+ }
36
+ const source = fs.readFileSync(registryPath, "utf8");
37
+ const match = source.match(
38
+ /(?:COMPONENT_IDS|\w+_COMPONENT_IDS)\s*=\s*\[([\s\S]*?)\]\s*as\s*const/,
39
+ );
40
+ if (!match) return null;
41
+ const ids = [...match[1].matchAll(/"([^"]+)"/g)].map((m) => m[1]);
42
+ const prefixMatch = source.match(
43
+ /DYNAMIC_TARGET_PREFIXES\s*=\s*\[([\s\S]*?)\]\s*as\s*const/,
44
+ );
45
+ const prefixes = prefixMatch
46
+ ? [...prefixMatch[1].matchAll(/"([^"]+)"/g)].map((m) => m[1])
47
+ : [];
48
+ return { ids: new Set(ids), prefixes };
49
+ }
50
+
51
+ function collectTsxFiles(slug) {
52
+ const root = path.join(prototypesDir, slug);
53
+ const files = [];
54
+ function walk(dir) {
55
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
56
+ const full = path.join(dir, entry.name);
57
+ if (entry.isDirectory()) walk(full);
58
+ else if (entry.name.endsWith(".tsx")) files.push(full);
59
+ }
60
+ }
61
+ walk(root);
62
+ return files;
63
+ }
64
+
65
+ function fileBaseId(filePath, slug) {
66
+ const rel = path.relative(path.join(prototypesDir, slug), filePath);
67
+ const base = path.basename(rel, ".tsx");
68
+ if (base === "index") return "page";
69
+ return kebabCase(base);
70
+ }
71
+
72
+ function findRenderableComponents(source, fileBase) {
73
+ const components = [];
74
+ const fnRegex =
75
+ /export\s+function\s+([A-Z][A-Za-z0-9]*)\s*\([^)]*\)\s*\{/g;
76
+ let m;
77
+ while ((m = fnRegex.exec(source)) !== null) {
78
+ const name = m[1];
79
+ const segment =
80
+ name === fileBase.replace(/-/g, "") ||
81
+ kebabCase(name) === fileBase
82
+ ? fileBase
83
+ : `${fileBase}.${kebabCase(name)}`;
84
+ components.push({ name, expectedId: segment });
85
+ }
86
+
87
+ const defaultExport = /export\s+default\s+function\s+([A-Z][A-Za-z0-9]*)/.exec(
88
+ source,
89
+ );
90
+ if (defaultExport) {
91
+ components.push({ name: defaultExport[1], expectedId: "page" });
92
+ }
93
+
94
+ return components;
95
+ }
96
+
97
+ function hasTargetWrapper(source) {
98
+ return (
99
+ source.includes("PrototypeTarget") ||
100
+ source.includes("PrototypeComponent") ||
101
+ source.includes("PrototypeControl")
102
+ );
103
+ }
104
+
105
+ let failed = false;
106
+
107
+ for (const slug of listPrototypeSlugs()) {
108
+ const registry = readRegistryIds(slug);
109
+ if (!registry) {
110
+ console.error(`[${slug}] Missing component-ids.ts`);
111
+ failed = true;
112
+ continue;
113
+ }
114
+
115
+ for (const file of collectTsxFiles(slug)) {
116
+ if (file.endsWith("component-ids.ts")) continue;
117
+ if (file.includes(`${path.sep}ui${path.sep}`)) continue;
118
+ if (file.includes(`${path.sep}_server${path.sep}`)) continue;
119
+
120
+ const source = fs.readFileSync(file, "utf8");
121
+ const fileBase = fileBaseId(file, slug);
122
+ const components = findRenderableComponents(source, fileBase);
123
+
124
+ if (components.length === 0) continue;
125
+
126
+ if (!hasTargetWrapper(source)) {
127
+ console.error(
128
+ `[${slug}] ${path.relative(repoRoot, file)}: no PrototypeTarget/PrototypeComponent/PrototypeControl wrapper`,
129
+ );
130
+ failed = true;
131
+ }
132
+
133
+ for (const { name, expectedId } of components) {
134
+ const allowed =
135
+ registry.ids.has(expectedId) ||
136
+ registry.prefixes.some((p) => expectedId.startsWith(p));
137
+ if (!allowed) {
138
+ console.error(
139
+ `[${slug}] ${name} in ${path.basename(file)}: expected registry id "${expectedId}"`,
140
+ );
141
+ failed = true;
142
+ }
143
+ }
144
+ }
145
+ }
146
+
147
+ if (failed) {
148
+ process.exit(1);
149
+ }
150
+
151
+ console.log("Prototype component id verification passed.");
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Ensures each prototype with a state map uses a single *-preview-states.ts
4
+ * registry that both the picker and canvas config import from.
5
+ *
6
+ * Runtime 1:1 parity (labels, variants, counts) is enforced when the registry
7
+ * module loads via definePreviewStateRegistry().
8
+ */
9
+ import fs from "node:fs";
10
+ import path from "node:path";
11
+ import {
12
+ resolveHostRoot,
13
+ resolveToolRoot,
14
+ } from "./lib/resolve-host-root.mjs";
15
+
16
+ const toolRoot = resolveToolRoot(import.meta.url);
17
+ const repoRoot = resolveHostRoot(toolRoot);
18
+ const prototypesDir = path.join(repoRoot, "src/prototypes");
19
+
20
+ function listPrototypeSlugs() {
21
+ return fs
22
+ .readdirSync(prototypesDir, { withFileTypes: true })
23
+ .filter((d) => d.isDirectory() && !d.name.startsWith("."))
24
+ .map((d) => d.name);
25
+ }
26
+
27
+ function walkFiles(dir, predicate) {
28
+ const files = [];
29
+
30
+ function walk(current) {
31
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
32
+ const full = path.join(current, entry.name);
33
+ if (entry.isDirectory()) walk(full);
34
+ else if (predicate(full, entry.name)) files.push(full);
35
+ }
36
+ }
37
+
38
+ walk(dir);
39
+ return files;
40
+ }
41
+
42
+ function findPreviewStateFiles(slug) {
43
+ return walkFiles(
44
+ path.join(prototypesDir, slug),
45
+ (_full, name) => name.endsWith("-preview-states.ts"),
46
+ );
47
+ }
48
+
49
+ function findStateCanvasConfigFiles(slug) {
50
+ return walkFiles(
51
+ path.join(prototypesDir, slug),
52
+ (_full, name) => name.endsWith("-state-canvas-config.tsx"),
53
+ );
54
+ }
55
+
56
+ function findStateSelectFiles(slug) {
57
+ return walkFiles(
58
+ path.join(prototypesDir, slug),
59
+ (_full, name) =>
60
+ name.endsWith(".tsx") &&
61
+ (name.includes("state-select") || name.includes("tab-select")),
62
+ );
63
+ }
64
+
65
+ function fileUsesPreviewStateRegistry(filePath, registryBasename) {
66
+ const source = fs.readFileSync(filePath, "utf8");
67
+ const registryModule = registryBasename.replace(/\.ts$/, "");
68
+ return (
69
+ source.includes(registryModule) ||
70
+ source.includes("PREVIEW_STATE_REGISTRY") ||
71
+ source.includes("_PREVIEW_OPTIONS")
72
+ );
73
+ }
74
+
75
+ let failed = false;
76
+
77
+ for (const slug of listPrototypeSlugs()) {
78
+ const previewStateFiles = findPreviewStateFiles(slug);
79
+ const canvasConfigFiles = findStateCanvasConfigFiles(slug);
80
+ const stateSelectFiles = findStateSelectFiles(slug);
81
+
82
+ if (canvasConfigFiles.length > 0 && previewStateFiles.length === 0) {
83
+ console.error(
84
+ `[${slug}] Has state canvas config but no *-preview-states.ts registry.`,
85
+ );
86
+ failed = true;
87
+ continue;
88
+ }
89
+
90
+ if (previewStateFiles.length === 0) {
91
+ continue;
92
+ }
93
+
94
+ if (previewStateFiles.length > 1) {
95
+ console.error(
96
+ `[${slug}] Expected one *-preview-states.ts file, found ${previewStateFiles.length}.`,
97
+ );
98
+ failed = true;
99
+ }
100
+
101
+ const registryFile = previewStateFiles[0];
102
+ const registryBasename = path.basename(registryFile);
103
+ const registrySource = fs.readFileSync(registryFile, "utf8");
104
+
105
+ if (!registrySource.includes("definePreviewStateRegistry")) {
106
+ console.error(
107
+ `[${slug}] ${path.relative(repoRoot, registryFile)} must call definePreviewStateRegistry().`,
108
+ );
109
+ failed = true;
110
+ }
111
+
112
+ for (const file of canvasConfigFiles) {
113
+ if (!fileUsesPreviewStateRegistry(file, registryBasename)) {
114
+ console.error(
115
+ `[${slug}] ${path.relative(repoRoot, file)} must import preview states from ${registryBasename}.`,
116
+ );
117
+ failed = true;
118
+ }
119
+ }
120
+
121
+ for (const file of stateSelectFiles) {
122
+ if (!fileUsesPreviewStateRegistry(file, registryBasename)) {
123
+ console.error(
124
+ `[${slug}] ${path.relative(repoRoot, file)} must import preview states from ${registryBasename}.`,
125
+ );
126
+ failed = true;
127
+ }
128
+ }
129
+ }
130
+
131
+ if (failed) {
132
+ process.exit(1);
133
+ }
134
+
135
+ console.log("Prototype preview state verification passed.");
package/src/app.ts ADDED
@@ -0,0 +1,4 @@
1
+ import "server-only";
2
+
3
+ export { createPrototypeGalleryPage } from "./config/create-prototype-gallery-page";
4
+ export { createPrototypeCatchAllPage } from "./config/create-prototype-host-app";
@@ -0,0 +1,21 @@
1
+ import { cn } from "@prototype/lib/utils";
2
+
3
+ type CodeBlockProps = {
4
+ children: string;
5
+ compact?: boolean;
6
+ className?: string;
7
+ };
8
+
9
+ export function CodeBlock({ children, compact = false, className }: CodeBlockProps) {
10
+ return (
11
+ <pre
12
+ className={cn(
13
+ "overflow-auto whitespace-pre-wrap rounded-md bg-muted font-mono text-muted-foreground",
14
+ compact ? "max-h-48 p-2 text-[13px] leading-relaxed" : "max-h-64 p-3 text-sm leading-relaxed",
15
+ className,
16
+ )}
17
+ >
18
+ {children}
19
+ </pre>
20
+ );
21
+ }