@dfosco/storyboard-core 3.2.0 → 3.3.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 (60) hide show
  1. package/dist/storyboard-ui.css +1 -0
  2. package/dist/storyboard-ui.js +26298 -0
  3. package/dist/storyboard-ui.js.map +1 -0
  4. package/dist/tailwind.css +1 -1
  5. package/package.json +24 -18
  6. package/scaffold/manifest.json +35 -0
  7. package/scaffold/scripts/link.sh +26 -0
  8. package/scaffold/scripts/unlink.sh +10 -0
  9. package/scaffold/skills/create/SKILL.md +501 -0
  10. package/scaffold/skills/storyboard/SKILL.md +360 -0
  11. package/scaffold/skills/update-storyboard/SKILL.md +16 -0
  12. package/scaffold/skills/update-storyboard/update-storyboard-packages.sh +26 -0
  13. package/scaffold/skills/vitest/GENERATION.md +5 -0
  14. package/scaffold/skills/vitest/SKILL.md +52 -0
  15. package/scaffold/skills/vitest/references/advanced-environments.md +264 -0
  16. package/scaffold/skills/vitest/references/advanced-projects.md +300 -0
  17. package/scaffold/skills/vitest/references/advanced-type-testing.md +237 -0
  18. package/scaffold/skills/vitest/references/advanced-vi.md +249 -0
  19. package/scaffold/skills/vitest/references/core-cli.md +166 -0
  20. package/scaffold/skills/vitest/references/core-config.md +174 -0
  21. package/scaffold/skills/vitest/references/core-describe.md +193 -0
  22. package/scaffold/skills/vitest/references/core-expect.md +219 -0
  23. package/scaffold/skills/vitest/references/core-hooks.md +244 -0
  24. package/scaffold/skills/vitest/references/core-test-api.md +233 -0
  25. package/scaffold/skills/vitest/references/features-concurrency.md +250 -0
  26. package/scaffold/skills/vitest/references/features-context.md +238 -0
  27. package/scaffold/skills/vitest/references/features-coverage.md +207 -0
  28. package/scaffold/skills/vitest/references/features-filtering.md +211 -0
  29. package/scaffold/skills/vitest/references/features-mocking.md +265 -0
  30. package/scaffold/skills/vitest/references/features-snapshots.md +207 -0
  31. package/scaffold/skills/worktree/SKILL.md +51 -0
  32. package/scaffold/storyboard.config.json +26 -0
  33. package/scaffold/svelte.config.js +1 -0
  34. package/scaffold/toolbar.config.json +4 -0
  35. package/src/ActionMenuButton.svelte +1 -1
  36. package/src/CanvasCreateMenu.svelte +1 -1
  37. package/src/CoreUIBar.svelte +20 -9
  38. package/src/CreateMenuButton.svelte +1 -1
  39. package/src/InspectorPanel.svelte +144 -49
  40. package/src/SidePanel.svelte +10 -10
  41. package/src/commandActions.js +1 -1
  42. package/src/comments/index.js +0 -3
  43. package/src/devtools.js +4 -1
  44. package/src/index.js +5 -2
  45. package/src/inspector/highlighter.js +3 -4
  46. package/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte +1 -1
  47. package/src/lib/components/ui/trigger-button/trigger-button.svelte +8 -4
  48. package/src/mountStoryboardCore.js +223 -0
  49. package/src/scaffold.js +100 -0
  50. package/src/stores/themeStore.ts +29 -8
  51. package/src/styles/tailwind.css +16 -0
  52. package/src/svelte-plugin-ui/components/Viewfinder.svelte +18 -0
  53. package/src/ui-entry.js +30 -0
  54. package/src/vite/server-plugin.js +8 -24
  55. package/src/workshop/features/createCanvas/CreateCanvasForm.svelte +24 -6
  56. package/src/workshop/features/createFlow/CreateFlowForm.svelte +1 -1
  57. package/src/workshop/features/createFlow/index.js +0 -1
  58. package/src/workshop/features/createPrototype/CreatePrototypeForm.svelte +1 -1
  59. package/src/workshop/features/createPrototype/index.js +0 -1
  60. /package/{core-ui.config.json → toolbar.config.json} +0 -0
@@ -0,0 +1,223 @@
1
+ /**
2
+ * mountStoryboardCore — single entry point for consumer apps.
3
+ *
4
+ * Initializes all storyboard systems (URL state, history, comments, devtools)
5
+ * and mounts the compiled Svelte UI. Consumers call this once at app startup.
6
+ *
7
+ * Usage:
8
+ * import { mountStoryboardCore } from '@dfosco/storyboard-core'
9
+ * import storyboardConfig from '../storyboard.config.json'
10
+ * mountStoryboardCore(storyboardConfig, { basePath: import.meta.env.BASE_URL })
11
+ */
12
+
13
+ import { installHideParamListener } from './interceptHideParams.js'
14
+ import { installHistorySync } from './hideMode.js'
15
+ import { installBodyClassSync } from './bodyClasses.js'
16
+ import { initCommentsConfig, isCommentsEnabled } from './comments/config.js'
17
+ import { initFeatureFlags } from './featureFlags.js'
18
+ import { initPlugins } from './plugins.js'
19
+ import { initUIConfig } from './uiConfig.js'
20
+
21
+ let _mounted = false
22
+
23
+ /**
24
+ * Apply the saved theme to Primer CSS attributes immediately, before
25
+ * React or Svelte mount. This prevents a flash of wrong-theme content.
26
+ * Reads the same `sb-color-scheme` localStorage key used by themeStore.
27
+ */
28
+ function applyEarlyTheme() {
29
+ if (typeof document === 'undefined') return
30
+
31
+ const stored =
32
+ typeof localStorage !== 'undefined'
33
+ ? localStorage.getItem('sb-color-scheme')
34
+ : null
35
+ const theme = stored || 'system'
36
+ const el = document.documentElement
37
+
38
+ // Resolve "system" to an actual theme for data-sb-theme
39
+ let resolved = theme
40
+ if (theme === 'system') {
41
+ resolved =
42
+ typeof window !== 'undefined' &&
43
+ window.matchMedia('(prefers-color-scheme: dark)').matches
44
+ ? 'dark'
45
+ : 'light'
46
+ }
47
+
48
+ el.setAttribute('data-sb-theme', resolved)
49
+
50
+ if (theme === 'system') {
51
+ el.setAttribute('data-color-mode', 'auto')
52
+ el.setAttribute('data-light-theme', 'light')
53
+ el.setAttribute('data-dark-theme', 'dark')
54
+ } else if (resolved.startsWith('dark')) {
55
+ el.setAttribute('data-color-mode', 'dark')
56
+ el.setAttribute('data-dark-theme', resolved)
57
+ el.setAttribute('data-light-theme', 'light')
58
+ } else {
59
+ el.setAttribute('data-color-mode', 'light')
60
+ el.setAttribute('data-light-theme', resolved)
61
+ el.setAttribute('data-dark-theme', 'dark')
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Inject the compiled UI stylesheet if not already present.
67
+ */
68
+ async function injectUIStyles() {
69
+ if (document.querySelector('[data-storyboard-ui-css]')) return
70
+
71
+ try {
72
+ // Dynamic import of CSS — Vite handles this as a side-effect import.
73
+ // In consumer repos: loads dist/storyboard-ui.css
74
+ // In source repo: Vite injects component styles via HMR
75
+ await import('@dfosco/storyboard-core/ui-runtime/style.css')
76
+ } catch {
77
+ // Graceful fallback — CSS may already be loaded by other means
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Mount the full storyboard core system.
83
+ *
84
+ * @param {object} [config={}] - Contents of storyboard.config.json
85
+ * @param {object} [options={}]
86
+ * @param {string} [options.basePath='/'] - Base URL path (e.g. import.meta.env.BASE_URL)
87
+ * @param {HTMLElement} [options.container=document.body] - Where to mount devtools
88
+ */
89
+ export async function mountStoryboardCore(config = {}, options = {}) {
90
+ if (_mounted) return
91
+ _mounted = true
92
+
93
+ const basePath = options.basePath || '/'
94
+
95
+ // Apply saved theme to DOM immediately — before Svelte/React mount
96
+ applyEarlyTheme()
97
+
98
+ // Initialize framework-agnostic systems
99
+ installHideParamListener()
100
+ installHistorySync()
101
+ installBodyClassSync()
102
+
103
+ // Initialize config-driven systems
104
+ if (config.featureFlags) {
105
+ initFeatureFlags(config.featureFlags)
106
+ }
107
+
108
+ if (config.plugins) {
109
+ initPlugins(config.plugins)
110
+ }
111
+
112
+ if (config.ui) {
113
+ initUIConfig(config.ui)
114
+ }
115
+
116
+ // Initialize comments config (framework-agnostic)
117
+ if (config.comments) {
118
+ initCommentsConfig(config, { basePath })
119
+ }
120
+
121
+ // Inject compiled UI styles
122
+ injectUIStyles()
123
+
124
+ // Load and merge toolbar config.
125
+ // Core defaults come from toolbar.config.json (bundled).
126
+ // Client can provide overrides via config.toolbar or a toolbar.config.json at repo root.
127
+ const { deepMerge } = await import('./loader.js')
128
+ const defaultConfig = (await import('../toolbar.config.json')).default
129
+ let toolbarConfig = config.toolbar
130
+ ? deepMerge(defaultConfig, config.toolbar)
131
+ : { ...defaultConfig }
132
+
133
+ // Inject repository URL from storyboard.config.json into the command menu
134
+ if (config.repository?.owner && config.repository?.name) {
135
+ const repoUrl = `https://github.com/${config.repository.owner}/${config.repository.name}`
136
+ const commandMenu = toolbarConfig.menus?.command
137
+ if (commandMenu?.actions) {
138
+ const repoAction = commandMenu.actions.find(a => a.id === 'core/repository')
139
+ if (repoAction) repoAction.url = repoUrl
140
+ }
141
+ }
142
+
143
+ // Skip all UI mounting when loaded inside a prototype embed iframe
144
+ const isEmbed = typeof window !== 'undefined' && new URLSearchParams(window.location.search).has('_sb_embed')
145
+ if (isEmbed) return
146
+
147
+ // Dynamically import the compiled UI bundle.
148
+ // Uses the package self-reference so resolution differs by context:
149
+ // Source repo: Vite alias overrides to src/ui-entry.js (source, HMR)
150
+ // Consumer repos: package.json exports resolve to dist/storyboard-ui.js (compiled)
151
+ const ui = await import('@dfosco/storyboard-core/ui-runtime')
152
+
153
+ // Mount devtools (CoreUIBar)
154
+ await ui.mountDevTools({
155
+ container: options.container,
156
+ basePath,
157
+ toolbarConfig,
158
+ })
159
+
160
+ // Mount comments system if configured
161
+ if (isCommentsEnabled()) {
162
+ ui.mountComments()
163
+ }
164
+
165
+ // Show pending workshop notifications (e.g. canvas created before Vite reload)
166
+ showPendingNotification(basePath)
167
+ }
168
+
169
+ /**
170
+ * Check sessionStorage for a pending workshop creation notification.
171
+ * Vite does a full-reload when new files are created, so the create form's
172
+ * success message is lost. This shows a temporary toast with the link.
173
+ */
174
+ function showPendingNotification(basePath) {
175
+ const KEYS = ['sb-canvas-created', 'sb-prototype-created', 'sb-flow-created']
176
+ for (const key of KEYS) {
177
+ try {
178
+ const raw = sessionStorage.getItem(key)
179
+ if (!raw) continue
180
+ sessionStorage.removeItem(key)
181
+ const { success: message, route } = JSON.parse(raw)
182
+ if (!message) continue
183
+ showToast(message, route, basePath)
184
+ return
185
+ } catch { /* ignore malformed session entry */ }
186
+ }
187
+ }
188
+
189
+ function showToast(message, route, basePath) {
190
+ const toast = document.createElement('div')
191
+ Object.assign(toast.style, {
192
+ position: 'fixed',
193
+ bottom: '7rem',
194
+ right: '1.5rem',
195
+ zIndex: '10000',
196
+ padding: '0.75rem 1rem',
197
+ borderRadius: '0.75rem',
198
+ background: 'var(--color-popover, #fff)',
199
+ color: 'var(--color-foreground, #1e293b)',
200
+ fontSize: '0.8125rem',
201
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif",
202
+ boxShadow: '0 8px 24px rgba(0,0,0,0.15)',
203
+ border: '1px solid var(--color-border, #cbd5e1)',
204
+ display: 'flex',
205
+ flexDirection: 'column',
206
+ gap: '0.25rem',
207
+ opacity: '0',
208
+ transition: 'opacity 0.15s ease',
209
+ maxWidth: '280px',
210
+ })
211
+
212
+ const href = route?.startsWith('/') ? (basePath.replace(/\/$/, '') + route) : route
213
+ toast.innerHTML = `<span style="font-weight:500">✓ ${message.replace(/</g, '&lt;')}</span>`
214
+ + (href ? `<a href="${href}" style="color:var(--color-primary, #0969da);text-decoration:underline;font-size:0.8125rem">Open canvas</a>` : '')
215
+
216
+ document.body.appendChild(toast)
217
+ requestAnimationFrame(() => { toast.style.opacity = '1' })
218
+
219
+ setTimeout(() => {
220
+ toast.style.opacity = '0'
221
+ setTimeout(() => toast.remove(), 300)
222
+ }, 8000)
223
+ }
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * storyboard-scaffold — sync scaffold files from @dfosco/storyboard-core.
4
+ *
5
+ * Reads scaffold/manifest.json and copies files to the consumer repo:
6
+ * - "scaffold" mode: only if target doesn't exist (never overwrites config)
7
+ * - "updateable" mode: always overwrites with latest (skills, scripts)
8
+ *
9
+ * Usage:
10
+ * npx storyboard-scaffold
11
+ */
12
+
13
+ import fs from 'node:fs'
14
+ import path from 'node:path'
15
+
16
+ const __dirname = path.dirname(new URL(import.meta.url).pathname)
17
+ const scaffoldRoot = path.resolve(__dirname, '..', 'scaffold')
18
+ const consumerRoot = process.cwd()
19
+
20
+ const manifestPath = path.join(scaffoldRoot, 'manifest.json')
21
+ if (!fs.existsSync(manifestPath)) {
22
+ console.error('❌ Could not find scaffold/manifest.json in @dfosco/storyboard-core')
23
+ process.exit(1)
24
+ }
25
+
26
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'))
27
+
28
+ function copyFileSync(src, dest) {
29
+ const dir = path.dirname(dest)
30
+ if (!fs.existsSync(dir)) {
31
+ fs.mkdirSync(dir, { recursive: true })
32
+ }
33
+ fs.copyFileSync(src, dest)
34
+ }
35
+
36
+ function copyDirSync(src, dest) {
37
+ if (!fs.existsSync(dest)) {
38
+ fs.mkdirSync(dest, { recursive: true })
39
+ }
40
+ const entries = fs.readdirSync(src, { withFileTypes: true })
41
+ for (const entry of entries) {
42
+ const srcPath = path.join(src, entry.name)
43
+ const destPath = path.join(dest, entry.name)
44
+ if (entry.isDirectory()) {
45
+ copyDirSync(srcPath, destPath)
46
+ } else {
47
+ copyFileSync(srcPath, destPath)
48
+ }
49
+ }
50
+ }
51
+
52
+ let created = 0
53
+ let updated = 0
54
+ let skipped = 0
55
+
56
+ for (const file of manifest.files) {
57
+ const srcPath = path.join(scaffoldRoot, path.relative('scaffold', file.source))
58
+ const destPath = path.join(consumerRoot, file.target)
59
+
60
+ if (file.directory) {
61
+ if (file.mode === 'updateable') {
62
+ copyDirSync(srcPath, destPath)
63
+ updated++
64
+ console.log(` ✔ Updated ${file.target} (sync)`)
65
+ } else {
66
+ if (!fs.existsSync(destPath)) {
67
+ copyDirSync(srcPath, destPath)
68
+ created++
69
+ console.log(` ✔ Created ${file.target} (scaffold)`)
70
+ } else {
71
+ skipped++
72
+ console.log(` ⏭ Skipped ${file.target} (already exists)`)
73
+ }
74
+ }
75
+ continue
76
+ }
77
+
78
+ if (file.mode === 'scaffold') {
79
+ if (fs.existsSync(destPath)) {
80
+ skipped++
81
+ console.log(` ⏭ Skipped ${file.target} (already exists)`)
82
+ } else {
83
+ copyFileSync(srcPath, destPath)
84
+ created++
85
+ console.log(` ✔ Created ${file.target} (scaffold)`)
86
+ }
87
+ } else if (file.mode === 'updateable') {
88
+ copyFileSync(srcPath, destPath)
89
+ updated++
90
+ console.log(` ✔ Updated ${file.target} (sync)`)
91
+ }
92
+
93
+ // Make shell scripts executable
94
+ if (file.target.endsWith('.sh')) {
95
+ fs.chmodSync(destPath, 0o755)
96
+ }
97
+ }
98
+
99
+ console.log('')
100
+ console.log(`✔ Scaffold complete: ${created} created, ${updated} updated, ${skipped} skipped.`)
@@ -1,9 +1,11 @@
1
1
  /**
2
2
  * Theme Store — manages the active color scheme for the entire app.
3
3
  *
4
- * Reads/writes `sb-color-scheme` in localStorage, sets the `data-sb-theme`
5
- * attribute on `<html>`, and dispatches a `storyboard:theme:changed` custom
6
- * event so that non-Svelte consumers (React ThemeProvider, etc.) can react.
4
+ * Reads/writes `sb-color-scheme` in localStorage, sets Primer CSS attributes
5
+ * (`data-color-mode`, `data-light-theme`, `data-dark-theme`) and the internal
6
+ * `data-sb-theme` attribute on `<html>`, and dispatches a
7
+ * `storyboard:theme:changed` custom event so that non-Svelte consumers
8
+ * (React ThemeProvider, etc.) can react.
7
9
  *
8
10
  * Supports a "system" value that follows the OS preference via
9
11
  * `prefers-color-scheme`, updating automatically when the user changes
@@ -86,9 +88,28 @@ function snapshot(theme: ThemeValue): ThemeState {
86
88
  let _current: ThemeValue = readStoredTheme()
87
89
  const _store = writable<ThemeState>(snapshot(_current))
88
90
 
89
- function _applyToDOM(resolved: string): void {
91
+ function _applyToDOM(theme: ThemeValue, resolved: string): void {
90
92
  if (typeof document === 'undefined') return
91
- document.documentElement.setAttribute('data-sb-theme', resolved)
93
+ const el = document.documentElement
94
+
95
+ // Internal attribute
96
+ el.setAttribute('data-sb-theme', resolved)
97
+
98
+ // Primer CSS attributes — these drive @primer/react ThemeProvider and
99
+ // Primer CSS custom-property layers without needing React state updates.
100
+ if (theme === 'system') {
101
+ el.setAttribute('data-color-mode', 'auto')
102
+ el.setAttribute('data-light-theme', 'light')
103
+ el.setAttribute('data-dark-theme', 'dark')
104
+ } else if (resolved.startsWith('dark')) {
105
+ el.setAttribute('data-color-mode', 'dark')
106
+ el.setAttribute('data-dark-theme', resolved)
107
+ el.setAttribute('data-light-theme', 'light')
108
+ } else {
109
+ el.setAttribute('data-color-mode', 'light')
110
+ el.setAttribute('data-light-theme', resolved)
111
+ el.setAttribute('data-dark-theme', 'dark')
112
+ }
92
113
  }
93
114
 
94
115
  function _dispatchEvent(theme: ThemeValue, resolved: string): void {
@@ -117,7 +138,7 @@ export function setTheme(value: ThemeValue): void {
117
138
 
118
139
  const state = snapshot(value)
119
140
  _store.set(state)
120
- _applyToDOM(state.resolved)
141
+ _applyToDOM(value, state.resolved)
121
142
  _dispatchEvent(value, state.resolved)
122
143
  }
123
144
 
@@ -137,7 +158,7 @@ if (typeof window !== 'undefined') {
137
158
  if (_current !== 'system') return
138
159
  const state = snapshot('system')
139
160
  _store.set(state)
140
- _applyToDOM(state.resolved)
161
+ _applyToDOM('system', state.resolved)
141
162
  _dispatchEvent('system', state.resolved)
142
163
  })
143
164
  }
@@ -146,4 +167,4 @@ if (typeof window !== 'undefined') {
146
167
  // Boot — apply the stored theme immediately on import
147
168
  // ---------------------------------------------------------------------------
148
169
 
149
- _applyToDOM(resolveTheme(_current))
170
+ _applyToDOM(_current, resolveTheme(_current))
@@ -1,4 +1,5 @@
1
1
  @import "tailwindcss";
2
+ @import "tw-animate-css";
2
3
  @source "../**/*.{svelte,js,ts}";
3
4
 
4
5
  @custom-variant dark (&:where([data-sb-theme^="dark"], [data-sb-theme^="dark"] *));
@@ -118,3 +119,18 @@
118
119
  --smooth-corners: 4;
119
120
  }
120
121
  }
122
+
123
+ /*
124
+ * Prevent unlayered CSS framework resets (e.g. Primer's `button { border-radius: 0 }`)
125
+ * from overriding layered Tailwind utilities on storyboard UI elements.
126
+ * `revert-layer` falls back to the value from the cascade layers (Tailwind utilities).
127
+ */
128
+ [data-slot="button"] {
129
+ border-radius: revert-layer;
130
+ font-weight: revert-layer;
131
+ }
132
+
133
+ [data-slot="panel-content"] select,
134
+ [data-slot="panel-content"] input {
135
+ border-radius: revert-layer;
136
+ }
@@ -502,6 +502,10 @@
502
502
  {/if}
503
503
  {:else}
504
504
  <!-- Canvas view -->
505
+ <div class="canvasWarning">
506
+ <Icon size={14} name="primer/alert" color="#9a6700" offsetY={-1} />
507
+ <span>Canvas is an experimental feature. Use with caution.</span>
508
+ </div>
505
509
  {#each canvasFolders as folder (folder.dirName)}
506
510
  <section class="folderGroup" class:folderGroupOpen={isExpanded(`folder:${folder.dirName}`)}>
507
511
  <button
@@ -958,4 +962,18 @@
958
962
  max-width: 720px;
959
963
  margin: 0 auto;
960
964
  }
965
+
966
+ .canvasWarning {
967
+ display: flex;
968
+ align-items: center;
969
+ gap: 8px;
970
+ padding: 10px 14px;
971
+ margin-bottom: 16px;
972
+ border-radius: 8px;
973
+ border: 1px solid var(--borderColor-default, var(--color-border, #d0d7de));
974
+ background: var(--bgColor-attention-muted, #3d2e00);
975
+ color: var(--fgColor-attention, #9a6700);
976
+ font-size: 13px;
977
+ line-height: 1.4;
978
+ }
961
979
  </style>
@@ -0,0 +1,30 @@
1
+ /**
2
+ * UI entry point — compiled into dist/storyboard-ui.js via Vite library build.
3
+ *
4
+ * This file is the entry for the pre-compiled Svelte UI bundle.
5
+ * It re-exports all UI mount functions that depend on Svelte.
6
+ * Consumers never import this directly — they use mountStoryboardCore()
7
+ * or the package self-reference '@dfosco/storyboard-core/ui-runtime'.
8
+ */
9
+
10
+ // Tailwind utility + component CSS — bundled into storyboard-ui.css
11
+ import './styles/tailwind.css'
12
+
13
+ // Comments CSS
14
+ import './comments/ui/comment-layout.css'
15
+ import './comments/ui/comments.css'
16
+
17
+ // Modes CSS (design mode body classes)
18
+ import './modes.css'
19
+
20
+ // CoreUIBar (floating toolbar)
21
+ export { mountDevTools, unmountDevTools } from './devtools.js'
22
+
23
+ // Comments UI (Svelte-based comment pins, windows, drawers)
24
+ export { mountComments } from './comments/ui/mount.js'
25
+
26
+ // Viewfinder dashboard (Svelte component)
27
+ export { mountViewfinder, unmountViewfinder } from './ui/viewfinder.ts'
28
+
29
+ // Design modes (Svelte component)
30
+ export { mountDesignModesUI as mountDesignModes } from './ui/design-modes.ts'
@@ -57,14 +57,6 @@ function readConfig(root) {
57
57
  }
58
58
  }
59
59
 
60
- /**
61
- * Check if any workshop feature is enabled.
62
- */
63
- function hasAnyWorkshopFeature(workshopConfig) {
64
- if (!workshopConfig?.features) return false
65
- return Object.values(workshopConfig.features).some(Boolean)
66
- }
67
-
68
60
  /**
69
61
  * Core storyboard server Vite plugin.
70
62
  */
@@ -114,17 +106,17 @@ export default function storyboardServer() {
114
106
  // Wire canvas API routes (always enabled — CRUD for .canvas.jsonl files)
115
107
  routeHandlers.set('canvas', createCanvasHandler({ root, sendJson }))
116
108
 
117
- // Watch core-ui.config.json for changes — trigger full reload so
109
+ // Watch toolbar.config.json for changes — trigger full reload so
118
110
  // CoreUIBar.svelte picks up menu/mode config changes during dev
119
- const coreUIConfigPath = path.resolve(
111
+ const toolbarConfigPath = path.resolve(
120
112
  path.dirname(new URL(import.meta.url).pathname),
121
- '../../core-ui.config.json'
113
+ '../../toolbar.config.json'
122
114
  )
123
- server.watcher.add(coreUIConfigPath)
115
+ server.watcher.add(toolbarConfigPath)
124
116
  server.watcher.on('change', (filePath) => {
125
- if (path.resolve(filePath) === coreUIConfigPath) {
117
+ if (path.resolve(filePath) === toolbarConfigPath) {
126
118
  // Invalidate the cached JSON module so Vite re-reads from disk
127
- const mods = server.moduleGraph.getModulesByFile(coreUIConfigPath)
119
+ const mods = server.moduleGraph.getModulesByFile(toolbarConfigPath)
128
120
  if (mods) {
129
121
  for (const mod of mods) {
130
122
  server.moduleGraph.invalidateModule(mod)
@@ -134,16 +126,8 @@ export default function storyboardServer() {
134
126
  }
135
127
  })
136
128
 
137
- // Inject workshop client UI when any feature is enabled
138
- if (hasAnyWorkshopFeature(workshopConfig)) {
139
- // Resolve the actual filesystem path for the mount script.
140
- // Use /@fs/ prefix so Vite serves it through its module pipeline.
141
- const mountPath = path.resolve(
142
- path.dirname(new URL(import.meta.url).pathname),
143
- '../workshop/ui/mount.ts'
144
- )
145
- clientScripts.push('/@fs' + mountPath)
146
- }
129
+ // Workshop client UI is now mounted by mountStoryboardCore() via the
130
+ // compiled UI bundle. No script injection needed.
147
131
 
148
132
  // Plugin registry for external plugins (future use).
149
133
  // Plugins call registerRoutes/registerClientScript in their setup().
@@ -26,6 +26,8 @@
26
26
  let loading = $state(true)
27
27
  let submitting = $state(false)
28
28
  let error: string | null = $state(null)
29
+ let success: string | null = $state(null)
30
+ let createdRoute: string | null = $state(null)
29
31
 
30
32
  const kebabName = $derived(
31
33
  name.replace(/[^a-zA-Z0-9\s_-]/g, '').trim().replace(/[\s_]+/g, '-').toLowerCase().replace(/-+/g, '-').replace(/^-|-$/g, '')
@@ -47,7 +49,20 @@
47
49
  return basePath.replace(/\/$/, '') + '/_storyboard/canvas'
48
50
  }
49
51
 
52
+ const CANVAS_SUCCESS_KEY = 'sb-canvas-created'
53
+
50
54
  onMount(async () => {
55
+ // Restore success state after Vite's full-reload on file creation
56
+ try {
57
+ const saved = sessionStorage.getItem(CANVAS_SUCCESS_KEY)
58
+ if (saved) {
59
+ const parsed = JSON.parse(saved)
60
+ success = parsed.success
61
+ createdRoute = parsed.route
62
+ sessionStorage.removeItem(CANVAS_SUCCESS_KEY)
63
+ }
64
+ } catch {}
65
+
51
66
  try {
52
67
  const res = await fetch(getApiUrl() + '/folders')
53
68
  if (res.ok) {
@@ -62,7 +77,7 @@
62
77
 
63
78
  async function submit() {
64
79
  if (!canSubmit) return
65
- submitting = true; error = null
80
+ submitting = true; error = null; success = null; createdRoute = null
66
81
  try {
67
82
  const res = await fetch(getApiUrl() + '/create', {
68
83
  method: 'POST', headers: { 'Content-Type': 'application/json' },
@@ -70,10 +85,12 @@
70
85
  })
71
86
  const data = await res.json()
72
87
  if (!res.ok) { error = data.error || 'Failed to create canvas'; return }
73
- // Add redirect param to current URL — survives Vite's full-reload
74
- const url = new URL(window.location.href)
75
- url.searchParams.set('redirect', data.route)
76
- window.history.replaceState(null, '', url.toString())
88
+ success = `Created ${data.path}`
89
+ createdRoute = data.route
90
+ // Persist success state — Vite does a full-reload when new files are created
91
+ try {
92
+ sessionStorage.setItem(CANVAS_SUCCESS_KEY, JSON.stringify({ success, route: createdRoute }))
93
+ } catch {}
77
94
  } catch (err: any) { error = err.message || 'Network error' } finally { submitting = false }
78
95
  }
79
96
 
@@ -86,7 +103,7 @@
86
103
  </Panel.Header>
87
104
 
88
105
  <!-- svelte-ignore a11y_no_static_element_interactions -->
89
- <div class="p-4 space-y-3" onkeydown={handleKeydown}>
106
+ <div class="p-4 pt-2 space-y-3" onkeydown={handleKeydown}>
90
107
  <div class="space-y-1">
91
108
  <Label for="sb-canvas-name">Name</Label>
92
109
  <Input id="sb-canvas-name" placeholder="e.g. design-overview" autocomplete="off" spellcheck="false" bind:value={name} />
@@ -118,6 +135,7 @@
118
135
  </div>
119
136
 
120
137
  {#if error}<Alert.Root variant="destructive"><Alert.Description>{error}</Alert.Description></Alert.Root>{/if}
138
+ {#if success}<Alert.Root><Alert.Description class="text-success">{success}{#if createdRoute} — <a href={createdRoute} class="underline font-medium">Go to canvas</a>{/if}</Alert.Description></Alert.Root>{/if}
121
139
  </div>
122
140
 
123
141
  <Panel.Footer>
@@ -102,7 +102,7 @@
102
102
  </Panel.Header>
103
103
 
104
104
  <!-- svelte-ignore a11y_no_static_element_interactions -->
105
- <div class="p-4 space-y-3" onkeydown={handleKeydown}>
105
+ <div class="p-4 pt-2 space-y-3" onkeydown={handleKeydown}>
106
106
  <div class="space-y-1">
107
107
  <Label for="sb-flow-name">Name</Label>
108
108
  <Input id="sb-flow-name" placeholder="e.g. empty-state" autocomplete="off" spellcheck="false" bind:value={name} />
@@ -10,7 +10,6 @@
10
10
  * - serverSetup: Called by the server plugin to register API routes
11
11
  */
12
12
 
13
- export { createFlowsHandler as serverSetup } from './server.js'
14
13
  import CreateFlowForm from './CreateFlowForm.svelte'
15
14
 
16
15
  export const name = 'createFlow'
@@ -121,7 +121,7 @@
121
121
  </Panel.Header>
122
122
 
123
123
  <!-- svelte-ignore a11y_no_static_element_interactions -->
124
- <div class="p-4 space-y-3" onkeydown={handleKeydown}>
124
+ <div class="p-4 pt-2 space-y-3" onkeydown={handleKeydown}>
125
125
  <div class="space-y-1">
126
126
  <Label for="sb-proto-name">Name</Label>
127
127
  <Input id="sb-proto-name" placeholder="e.g. my-prototype" autocomplete="off" spellcheck="false" bind:value={name} />
@@ -10,7 +10,6 @@
10
10
  * - serverSetup: Called by the server plugin to register API routes
11
11
  */
12
12
 
13
- export { createPrototypesHandler as serverSetup } from './server.js'
14
13
  import CreatePrototypeForm from './CreatePrototypeForm.svelte'
15
14
 
16
15
  export const name = 'createPrototype'
File without changes