@dfosco/storyboard-core 4.0.0-beta.2 → 4.0.0-beta.21

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 (73) hide show
  1. package/dist/storyboard-ui.css +1 -1
  2. package/dist/storyboard-ui.js +11882 -11126
  3. package/dist/storyboard-ui.js.map +1 -1
  4. package/dist/tailwind.css +1 -1
  5. package/package.json +11 -3
  6. package/paste.config.json +54 -0
  7. package/scaffold/deploy.yml +101 -0
  8. package/scaffold/githooks/pre-push +114 -0
  9. package/scaffold/manifest.json +11 -0
  10. package/scaffold/storyboard.config.json +4 -1
  11. package/src/ActionMenuButton.svelte +12 -2
  12. package/src/CanvasCreateMenu.svelte +228 -10
  13. package/src/CanvasSnap.svelte +2 -0
  14. package/src/CoreUIBar.svelte +152 -3
  15. package/src/CreateMenuButton.svelte +4 -1
  16. package/src/InspectorPanel.svelte +2 -0
  17. package/src/PwaInstallBanner.svelte +124 -0
  18. package/src/autosync/server.js +99 -111
  19. package/src/autosync/server.test.js +0 -7
  20. package/src/canvas/collision.js +206 -0
  21. package/src/canvas/collision.test.js +271 -0
  22. package/src/canvas/deriveCanvasId.test.js +40 -0
  23. package/src/canvas/identity.js +107 -0
  24. package/src/canvas/identity.test.js +100 -0
  25. package/src/canvas/server.js +285 -31
  26. package/src/canvasConfig.js +56 -0
  27. package/src/canvasConfig.test.js +42 -0
  28. package/src/cli/canvasAdd.js +185 -0
  29. package/src/cli/canvasRead.js +208 -0
  30. package/src/cli/code.js +67 -0
  31. package/src/cli/create.js +339 -72
  32. package/src/cli/dev-helpers.js +53 -0
  33. package/src/cli/dev-helpers.test.js +53 -0
  34. package/src/cli/dev.js +245 -26
  35. package/src/cli/flags.js +174 -0
  36. package/src/cli/flags.test.js +155 -0
  37. package/src/cli/index.js +84 -13
  38. package/src/cli/intro.js +37 -0
  39. package/src/cli/proxy.js +127 -6
  40. package/src/cli/proxy.test.js +63 -0
  41. package/src/cli/schemas.js +200 -0
  42. package/src/cli/serverUrl.js +56 -0
  43. package/src/cli/setup.js +130 -20
  44. package/src/cli/snapshots.js +335 -0
  45. package/src/cli/updateVersion.js +54 -3
  46. package/src/configSchema.js +125 -0
  47. package/src/configSchema.test.js +68 -0
  48. package/src/index.js +5 -0
  49. package/src/inspector/highlighter.js +10 -2
  50. package/src/lib/components/ui/trigger-button/trigger-button.svelte +1 -1
  51. package/src/loader.js +21 -2
  52. package/src/loader.test.js +63 -1
  53. package/src/mobileViewport.js +57 -0
  54. package/src/mobileViewport.test.js +68 -0
  55. package/src/mountStoryboardCore.js +61 -7
  56. package/src/rename-watcher/config.json +23 -0
  57. package/src/rename-watcher/watcher.js +538 -0
  58. package/src/svelte-plugin-ui/components/Viewfinder.svelte +6 -17
  59. package/src/tools/handlers/flows.js +6 -7
  60. package/src/viewfinder.js +21 -9
  61. package/src/viewfinder.test.js +2 -2
  62. package/src/vite/server-plugin.js +150 -7
  63. package/src/workshop/features/createCanvas/CreateCanvasForm.svelte +8 -2
  64. package/src/workshop/features/createFlow/CreateFlowForm.svelte +1 -1
  65. package/src/workshop/features/createPage/CreatePageForm.svelte +1 -1
  66. package/src/workshop/features/createPrototype/CreatePrototypeForm.svelte +2 -2
  67. package/src/workshop/features/createStory/CreateStoryForm.svelte +160 -0
  68. package/src/workshop/features/createStory/index.js +14 -0
  69. package/src/workshop/features/registry.js +2 -0
  70. package/src/worktree/port.js +57 -1
  71. package/src/worktree/port.test.js +91 -1
  72. package/toolbar.config.json +3 -3
  73. package/widgets.config.json +132 -27
@@ -0,0 +1,185 @@
1
+ /**
2
+ * storyboard canvas add <widget-type> — Add a widget to an existing canvas.
3
+ *
4
+ * Usage:
5
+ * storyboard canvas add sticky-note --canvas my-canvas
6
+ * storyboard canvas add markdown --canvas my-canvas --x 100 --y 200
7
+ * storyboard canvas add prototype --canvas my-canvas --props '{"src":"my-proto"}'
8
+ *
9
+ * Known widget types: sticky-note, markdown, prototype
10
+ * The server accepts any type string — new widget types work automatically.
11
+ */
12
+
13
+ import * as p from '@clack/prompts'
14
+ import { parseFlags, hasFlags, formatFlagHelp } from './flags.js'
15
+ import { widgetSchema } from './schemas.js'
16
+ import { ensureDevServer, serverPost, getServerUrl } from './create.js'
17
+
18
+ const KNOWN_TYPES = ['sticky-note', 'markdown', 'prototype', 'story']
19
+
20
+ async function canvasAdd() {
21
+ // argv: storyboard canvas add [type] [--flags]
22
+ // process.argv: [node, script, 'canvas', 'add', ...rest]
23
+ const rest = process.argv.slice(4)
24
+
25
+ if (rest.includes('--help') || rest.includes('-h')) {
26
+ console.log(`\n canvas add flags:\n`)
27
+ console.log(` Positional: <widget-type> Widget type (${KNOWN_TYPES.join(', ')})\n`)
28
+ console.log(formatFlagHelp(widgetSchema))
29
+ console.log('')
30
+ process.exit(0)
31
+ }
32
+
33
+ // Extract positional widget type (first non-flag arg)
34
+ let widgetType = ''
35
+ const flagTokens = []
36
+ for (const token of rest) {
37
+ if (!widgetType && !token.startsWith('-')) {
38
+ widgetType = token
39
+ } else {
40
+ flagTokens.push(token)
41
+ }
42
+ }
43
+
44
+ const flagMode = hasFlags(rest) || Boolean(widgetType)
45
+ const { flags, errors } = flagMode ? parseFlags(flagTokens, widgetSchema) : { flags: {}, errors: [] }
46
+
47
+ if (errors.length) {
48
+ for (const e of errors) p.log.error(e)
49
+ process.exit(1)
50
+ }
51
+
52
+ p.intro('storyboard canvas add')
53
+ await ensureDevServer()
54
+
55
+ // Widget type
56
+ if (!widgetType) {
57
+ widgetType = await p.select({
58
+ message: 'Widget type',
59
+ options: KNOWN_TYPES.map((t) => ({ value: t, label: t })),
60
+ })
61
+ if (p.isCancel(widgetType)) return process.exit(0)
62
+ }
63
+
64
+ // Canvas name
65
+ const canvasName = flags.canvas || await (async () => {
66
+ // Try to fetch available canvases
67
+ let canvases = []
68
+ try {
69
+ const base = getServerUrl()
70
+ const res = await fetch(`${base}/_storyboard/canvas/list`)
71
+ if (res.ok) {
72
+ const data = await res.json()
73
+ canvases = data.canvases || data || []
74
+ }
75
+ } catch {}
76
+
77
+ if (canvases.length > 0) {
78
+ const choice = await p.select({
79
+ message: 'Canvas',
80
+ options: canvases.map((c) => ({ value: c.name || c, label: c.name || c })),
81
+ })
82
+ if (p.isCancel(choice)) process.exit(0)
83
+ return choice
84
+ }
85
+
86
+ const v = await p.text({
87
+ message: 'Canvas name',
88
+ placeholder: 'my-canvas',
89
+ validate: (v) => { if (!v) return 'Canvas name is required' },
90
+ })
91
+ if (p.isCancel(v)) process.exit(0)
92
+ return v
93
+ })()
94
+
95
+ const x = flags.x ?? 0
96
+ const y = flags.y ?? 0
97
+
98
+ let props = {}
99
+ if (flags.props) {
100
+ try {
101
+ props = JSON.parse(flags.props)
102
+ } catch {
103
+ p.log.error('--props must be valid JSON')
104
+ process.exit(1)
105
+ }
106
+ }
107
+
108
+ // Story-specific: prompt for storyId and exportName if not in --props
109
+ if (widgetType === 'story' && !props.storyId) {
110
+ // Try to fetch available stories from the dev server
111
+ let stories = []
112
+ try {
113
+ const base = getServerUrl()
114
+ const res = await fetch(`${base}/_storyboard/stories/list`)
115
+ if (res.ok) {
116
+ const data = await res.json()
117
+ stories = data.stories || []
118
+ }
119
+ } catch {}
120
+
121
+ if (stories.length > 0) {
122
+ const storyId = await (async () => {
123
+ const choice = await p.select({
124
+ message: 'Story',
125
+ options: stories.map((s) => ({ value: s.name, label: s.name, hint: s.route || '' })),
126
+ })
127
+ if (p.isCancel(choice)) process.exit(0)
128
+ return choice
129
+ })()
130
+ props.storyId = storyId
131
+ } else {
132
+ const storyId = await (async () => {
133
+ const v = await p.text({
134
+ message: 'Story ID',
135
+ placeholder: 'button-patterns',
136
+ validate: (v) => { if (!v) return 'Story ID is required' },
137
+ })
138
+ if (p.isCancel(v)) process.exit(0)
139
+ return v
140
+ })()
141
+ props.storyId = storyId
142
+ }
143
+
144
+ if (!props.exportName) {
145
+ const exportName = await (async () => {
146
+ const v = await p.text({
147
+ message: 'Export name (leave empty for all)',
148
+ placeholder: 'Default',
149
+ })
150
+ if (p.isCancel(v)) process.exit(0)
151
+ return v
152
+ })()
153
+ if (exportName) props.exportName = exportName
154
+ }
155
+
156
+ if (!props.width) props.width = 600
157
+ if (!props.height) props.height = 400
158
+ }
159
+
160
+ // Submit
161
+ const s = p.spinner()
162
+ s.start(`Adding ${widgetType} widget...`)
163
+
164
+ try {
165
+ const result = await serverPost('/_storyboard/canvas/widget', {
166
+ name: canvasName,
167
+ type: widgetType,
168
+ props,
169
+ position: { x, y },
170
+ })
171
+ s.stop(`Widget added!`)
172
+ if (result.id) {
173
+ p.log.success(` ${widgetType} → ${canvasName} (id: ${result.id})`)
174
+ } else {
175
+ p.log.success(` ${widgetType} → ${canvasName}`)
176
+ }
177
+ p.outro('')
178
+ } catch (err) {
179
+ s.stop('Failed to add widget')
180
+ p.log.error(err.message)
181
+ p.outro('')
182
+ }
183
+ }
184
+
185
+ canvasAdd()
@@ -0,0 +1,208 @@
1
+ /**
2
+ * storyboard canvas read — Read canvas state and list widgets with their IDs, URLs, and content.
3
+ *
4
+ * Usage:
5
+ * storyboard canvas read my-canvas List all widgets
6
+ * storyboard canvas read my-canvas --json Output as JSON
7
+ * storyboard canvas read my-canvas --id abc Get a specific widget by ID
8
+ *
9
+ * Output includes:
10
+ * - Widget ID
11
+ * - Widget type
12
+ * - Position (x, y)
13
+ * - Content (text, url, src depending on type)
14
+ * - File path (for images)
15
+ */
16
+
17
+ import * as p from '@clack/prompts'
18
+ import { getServerUrl } from './serverUrl.js'
19
+
20
+ const dim = (s) => `\x1b[2m${s}\x1b[0m`
21
+ const bold = (s) => `\x1b[1m${s}\x1b[0m`
22
+ const cyan = (s) => `\x1b[36m${s}\x1b[0m`
23
+
24
+ async function checkServer() {
25
+ try {
26
+ await fetch(getServerUrl(), { signal: AbortSignal.timeout(2000) })
27
+ return true
28
+ } catch {
29
+ return false
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Extract the primary content from a widget based on its type.
35
+ */
36
+ function getWidgetContent(widget) {
37
+ const { type, props = {} } = widget
38
+ switch (type) {
39
+ case 'sticky-note':
40
+ return { content: props.text || '', contentType: 'text' }
41
+ case 'markdown':
42
+ return { content: props.content || '', contentType: 'markdown' }
43
+ case 'prototype':
44
+ return { content: props.src || '', contentType: 'url', url: props.src }
45
+ case 'figma-embed':
46
+ return { content: props.url || '', contentType: 'url', url: props.url }
47
+ case 'link-preview':
48
+ return { content: props.url || '', contentType: 'url', url: props.url }
49
+ case 'image':
50
+ return {
51
+ content: props.src || '',
52
+ contentType: 'image',
53
+ url: props.src ? `/_storyboard/canvas/images/${props.src}` : '',
54
+ filePath: props.src ? `assets/canvas/images/${props.src}` : '',
55
+ }
56
+ default:
57
+ return { content: JSON.stringify(props), contentType: 'props' }
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Format a widget for human-readable output.
63
+ */
64
+ function formatWidget(widget) {
65
+ const { id, type, position = {} } = widget
66
+ const { content, contentType, url, filePath } = getWidgetContent(widget)
67
+
68
+ const lines = []
69
+ lines.push(`${bold(id)} ${dim(`(${type})`)}`)
70
+ lines.push(` Position: ${position.x ?? 0}, ${position.y ?? 0}`)
71
+
72
+ if (contentType === 'image') {
73
+ lines.push(` File: ${cyan(filePath)}`)
74
+ if (content) lines.push(` Filename: ${content}`)
75
+ } else if (contentType === 'url') {
76
+ lines.push(` URL: ${cyan(url || content)}`)
77
+ } else if (content) {
78
+ const preview = content.length > 80 ? content.slice(0, 77) + '...' : content
79
+ lines.push(` Content: ${preview}`)
80
+ }
81
+
82
+ return lines.join('\n')
83
+ }
84
+
85
+ async function canvasRead() {
86
+ const args = process.argv.slice(4)
87
+
88
+ if (args.includes('--help') || args.includes('-h')) {
89
+ console.log(`
90
+ canvas read — Read canvas state and list widgets
91
+
92
+ Usage:
93
+ storyboard canvas read <canvas-name> List all widgets
94
+ storyboard canvas read <canvas-name> --json Output as JSON
95
+ storyboard canvas read <canvas-name> --id <widget-id> Get specific widget
96
+
97
+ Output includes widget ID, type, position, content, URLs, and file paths.
98
+ `)
99
+ process.exit(0)
100
+ }
101
+
102
+ // Parse args
103
+ let canvasName = ''
104
+ let outputJson = false
105
+ let widgetId = ''
106
+
107
+ for (let i = 0; i < args.length; i++) {
108
+ const arg = args[i]
109
+ if (arg === '--json') {
110
+ outputJson = true
111
+ } else if (arg === '--id' && args[i + 1]) {
112
+ widgetId = args[++i]
113
+ } else if (!arg.startsWith('-') && !canvasName) {
114
+ canvasName = arg
115
+ }
116
+ }
117
+
118
+ // Check server
119
+ const serverUp = await checkServer()
120
+ if (!serverUp) {
121
+ p.log.error('Dev server is not running. Start it with: storyboard dev')
122
+ process.exit(1)
123
+ }
124
+
125
+ const base = getServerUrl()
126
+
127
+ // If no canvas name, list available canvases
128
+ if (!canvasName) {
129
+ try {
130
+ const res = await fetch(`${base}/_storyboard/canvas/list`)
131
+ if (!res.ok) throw new Error(`${res.status} ${res.statusText}`)
132
+ const data = await res.json()
133
+ const canvases = data.canvases || []
134
+
135
+ if (outputJson) {
136
+ console.log(JSON.stringify(canvases, null, 2))
137
+ } else {
138
+ if (canvases.length === 0) {
139
+ p.log.info('No canvases found')
140
+ } else {
141
+ console.log('\nAvailable canvases:\n')
142
+ for (const c of canvases) {
143
+ console.log(` ${bold(c.name)} ${dim(`(${c.widgetCount} widgets)`)}`)
144
+ }
145
+ console.log('')
146
+ }
147
+ }
148
+ } catch (err) {
149
+ p.log.error(`Failed to list canvases: ${err.message}`)
150
+ process.exit(1)
151
+ }
152
+ return
153
+ }
154
+
155
+ // Read canvas state
156
+ try {
157
+ let url = `${base}/_storyboard/canvas/read?name=${encodeURIComponent(canvasName)}`
158
+ if (widgetId) url += `&widget=${encodeURIComponent(widgetId)}`
159
+ const res = await fetch(url)
160
+ if (!res.ok) {
161
+ const text = await res.text().catch(() => '')
162
+ throw new Error(`${res.status} ${res.statusText}${text ? ': ' + text : ''}`)
163
+ }
164
+ const data = await res.json()
165
+ const widgets = data.widgets || []
166
+
167
+ // If filtering by widget ID
168
+ if (widgetId) {
169
+ const widget = widgets[0]
170
+ if (!widget) {
171
+ p.log.error(`Widget "${widgetId}" not found in canvas "${canvasName}"`)
172
+ process.exit(1)
173
+ }
174
+
175
+ if (outputJson) {
176
+ const enriched = { ...widget, ...getWidgetContent(widget) }
177
+ console.log(JSON.stringify(enriched, null, 2))
178
+ } else {
179
+ console.log('')
180
+ console.log(formatWidget(widget))
181
+ console.log('')
182
+ }
183
+ return
184
+ }
185
+
186
+ // List all widgets
187
+ if (outputJson) {
188
+ const enriched = widgets.map((w) => ({ ...w, ...getWidgetContent(w) }))
189
+ console.log(JSON.stringify({ ...data, widgets: enriched }, null, 2))
190
+ } else {
191
+ console.log(`\n${bold(data.title || canvasName)} ${dim(`(${widgets.length} widgets)`)}\n`)
192
+
193
+ if (widgets.length === 0) {
194
+ console.log(' No widgets on this canvas.\n')
195
+ } else {
196
+ for (const widget of widgets) {
197
+ console.log(formatWidget(widget))
198
+ console.log('')
199
+ }
200
+ }
201
+ }
202
+ } catch (err) {
203
+ p.log.error(`Failed to read canvas: ${err.message}`)
204
+ process.exit(1)
205
+ }
206
+ }
207
+
208
+ canvasRead()
@@ -0,0 +1,67 @@
1
+ /**
2
+ * storyboard code [branch] — Open a worktree in VS Code.
3
+ *
4
+ * Usage:
5
+ * storyboard code # open current worktree or repo root
6
+ * storyboard code main # open repo root
7
+ * storyboard code <branch> # open .worktrees/<branch>/
8
+ */
9
+
10
+ import * as p from '@clack/prompts'
11
+ import { execFileSync } from 'child_process'
12
+ import { existsSync } from 'fs'
13
+ import { resolve } from 'path'
14
+ import { detectWorktreeName, repoRoot, worktreeDir, listWorktrees } from '../worktree/port.js'
15
+
16
+ const branch = process.argv[3] || undefined
17
+
18
+ p.intro('storyboard code')
19
+
20
+ function openInCode(dir) {
21
+ try {
22
+ execFileSync('code', [dir], { stdio: 'inherit' })
23
+ return true
24
+ } catch {
25
+ return false
26
+ }
27
+ }
28
+
29
+ const root = repoRoot()
30
+
31
+ if (!branch) {
32
+ // No argument — open current directory
33
+ const name = detectWorktreeName()
34
+ const dir = name === 'main' ? root : worktreeDir(name)
35
+ if (openInCode(dir)) {
36
+ p.outro(`Opened ${name === 'main' ? 'repo root' : `.worktrees/${name}/`}`)
37
+ } else {
38
+ p.log.error('Could not open VS Code. Is the `code` CLI installed?')
39
+ p.log.info('Run `npx storyboard setup` to install it, or open VS Code and run:')
40
+ p.log.info(' Cmd+Shift+P → "Shell Command: Install \'code\' command in PATH"')
41
+ process.exit(1)
42
+ }
43
+ } else if (branch === 'main') {
44
+ if (openInCode(root)) {
45
+ p.outro('Opened repo root')
46
+ } else {
47
+ p.log.error('Could not open VS Code.')
48
+ process.exit(1)
49
+ }
50
+ } else {
51
+ const dir = worktreeDir(branch)
52
+ if (!existsSync(resolve(dir, '.git'))) {
53
+ // List available worktrees as a hint
54
+ const available = listWorktrees()
55
+ p.log.error(`Worktree "${branch}" does not exist.`)
56
+ if (available.length > 0) {
57
+ p.log.info(`Available worktrees: ${available.join(', ')}`)
58
+ }
59
+ process.exit(1)
60
+ }
61
+ if (openInCode(dir)) {
62
+ p.outro(`Opened .worktrees/${branch}/`)
63
+ } else {
64
+ p.log.error('Could not open VS Code.')
65
+ process.exit(1)
66
+ }
67
+ }