@dfosco/storyboard-react 3.11.0-beta.11 → 3.11.0-beta.12

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.
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@dfosco/storyboard-react",
3
- "version": "3.11.0-beta.11",
3
+ "version": "3.11.0-beta.12",
4
4
  "type": "module",
5
5
  "dependencies": {
6
- "@dfosco/storyboard-core": "3.11.0-beta.11",
7
- "@dfosco/tiny-canvas": "3.11.0-beta.11",
6
+ "@dfosco/storyboard-core": "3.11.0-beta.12",
7
+ "@dfosco/tiny-canvas": "3.11.0-beta.12",
8
8
  "@neodrag/react": "^2.3.1",
9
9
  "glob": "^11.0.0",
10
10
  "jsonc-parser": "^3.3.1"
@@ -1,3 +1,4 @@
1
1
  // Stub for the Vite virtual module used by context.jsx
2
2
  // The actual init() seeding is done in each test's beforeEach
3
+ export const canvases = {}
3
4
  export default {}
@@ -3,7 +3,7 @@
3
3
  overflow: hidden;
4
4
  background: var(--bgColor-default, #ffffff);
5
5
  border: 3px solid var(--borderColor-default, #d0d7de);
6
- border-radius: 8px;
6
+ border-radius: 12px;
7
7
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
8
8
  }
9
9
 
@@ -3,7 +3,7 @@
3
3
  overflow: hidden;
4
4
  background: var(--bgColor-default, #ffffff);
5
5
  border: 3px solid var(--borderColor-default, #d0d7de);
6
- border-radius: 8px;
6
+ border-radius: 12px;
7
7
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
8
8
  }
9
9
 
package/src/context.jsx CHANGED
@@ -22,6 +22,11 @@ function matchCanvasRoute(pathname) {
22
22
  return canvasRouteMap.get(normalized) || null
23
23
  }
24
24
 
25
+ function isCanvasPath(pathname) {
26
+ const normalized = pathname.replace(/\/+$/, '') || '/'
27
+ return normalized === '/canvas' || normalized.startsWith('/canvas/')
28
+ }
29
+
25
30
  /**
26
31
  * Derives the top-level prototype name from a pathname.
27
32
  * "/Dashboard" → "Dashboard", "/Dashboard/sub" → "Dashboard"
@@ -62,6 +67,10 @@ export default function StoryboardProvider({ flowName, sceneName, recordName, re
62
67
 
63
68
  // Canvas route detection — matches current URL against registered canvas routes
64
69
  const canvasName = useMemo(() => matchCanvasRoute(location.pathname), [location.pathname])
70
+ const isMissingCanvasRoute = useMemo(
71
+ () => isCanvasPath(location.pathname) && !canvasName,
72
+ [location.pathname, canvasName],
73
+ )
65
74
 
66
75
  const searchParams = new URLSearchParams(location.search)
67
76
  const sceneParam = searchParams.get('flow') || searchParams.get('scene')
@@ -70,7 +79,7 @@ export default function StoryboardProvider({ flowName, sceneName, recordName, re
70
79
 
71
80
  // Resolve flow name with prototype scoping (skip for canvas pages)
72
81
  const activeFlowName = useMemo(() => {
73
- if (canvasName) return null
82
+ if (canvasName || isMissingCanvasRoute) return null
74
83
  const requested = sceneParam || flowName || sceneName
75
84
  if (requested) {
76
85
  // Allow fully-scoped flow names from URLs/widgets without re-prefixing
@@ -94,7 +103,7 @@ export default function StoryboardProvider({ flowName, sceneName, recordName, re
94
103
  // 4. Global default — or null if no flow exists at all
95
104
  if (flowExists('default')) return 'default'
96
105
  return null
97
- }, [canvasName, sceneParam, flowName, sceneName, prototypeName, pageFlow])
106
+ }, [canvasName, isMissingCanvasRoute, sceneParam, flowName, sceneName, prototypeName, pageFlow])
98
107
 
99
108
  // Auto-install body class sync (sb-key--value classes on <body>)
100
109
  useEffect(() => installBodyClassSync(), [])
@@ -117,7 +126,7 @@ export default function StoryboardProvider({ flowName, sceneName, recordName, re
117
126
 
118
127
  // Skip flow loading for canvas pages and flow-less pages
119
128
  const { data, error } = useMemo(() => {
120
- if (canvasName) return { data: null, error: null }
129
+ if (canvasName || isMissingCanvasRoute) return { data: null, error: null }
121
130
  if (!activeFlowName) return { data: {}, error: null }
122
131
  try {
123
132
  let flowData = loadFlow(activeFlowName)
@@ -136,7 +145,7 @@ export default function StoryboardProvider({ flowName, sceneName, recordName, re
136
145
  } catch (err) {
137
146
  return { data: null, error: err.message }
138
147
  }
139
- }, [canvasName, activeFlowName, recordName, recordParam, params, prototypeName])
148
+ }, [canvasName, isMissingCanvasRoute, activeFlowName, recordName, recordParam, params, prototypeName])
140
149
 
141
150
  // Canvas pages get their own rendering path — no flow data needed
142
151
  if (canvasName) {
@@ -157,6 +166,27 @@ export default function StoryboardProvider({ flowName, sceneName, recordName, re
157
166
  )
158
167
  }
159
168
 
169
+ if (isMissingCanvasRoute) {
170
+ const currentUrl = `${location.pathname}${location.search}`
171
+ const truncatedUrl = currentUrl.length > 60
172
+ ? currentUrl.slice(0, 60) + '…'
173
+ : currentUrl
174
+
175
+ return (
176
+ <main className={styles.container}>
177
+ <div className={styles.banner}>
178
+ <strong>Canvas not found</strong>
179
+ No canvas matches this route.
180
+ </div>
181
+ <p className={styles.meta}>
182
+ Tried to open{' '}
183
+ <a href={currentUrl} title={currentUrl}>{truncatedUrl}</a>
184
+ </p>
185
+ <a className={styles.homeLink} href="/">← Go to index page</a>
186
+ </main>
187
+ )
188
+ }
189
+
160
190
  const value = {
161
191
  data,
162
192
  error,
@@ -280,4 +280,17 @@ describe('StoryboardProvider', () => {
280
280
  )
281
281
  expect(screen.getByTestId('ctx')).toHaveTextContent('Global Default')
282
282
  })
283
+
284
+ it('shows a simple 404 for unknown canvas routes with an index link', () => {
285
+ mockUseLocation.mockReturnValue({ pathname: '/canvas/unknown-board', search: '', hash: '' })
286
+
287
+ render(
288
+ <StoryboardProvider>
289
+ <ContextReader />
290
+ </StoryboardProvider>,
291
+ )
292
+
293
+ expect(screen.getByText('Canvas not found')).toBeInTheDocument()
294
+ expect(screen.getByRole('link', { name: /go to index page/i })).toHaveAttribute('href', '/')
295
+ })
283
296
  })