@exegia/corpora-ui 0.18.0 → 0.20.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 (78) hide show
  1. package/dist-lib/components/blocks/nav/sidebar/__tests__/use-ai-sidebar.test.d.ts +1 -0
  2. package/dist-lib/components/blocks/nav/sidebar/ai-sidebar.d.ts +11 -2
  3. package/dist-lib/components/blocks/nav/sidebar/type.d.ts +83 -1
  4. package/dist-lib/components/blocks/nav/sidebar/use-ai-sidebar.d.ts +22 -31
  5. package/dist-lib/components/blocks/nav/sidebar/utils.d.ts +5 -0
  6. package/dist-lib/components/blocks/scaffold/constants.d.ts +8 -0
  7. package/dist-lib/components/blocks/scaffold/index.d.ts +3 -1
  8. package/dist-lib/components/blocks/scaffold/panel-menu-button.d.ts +4 -3
  9. package/dist-lib/components/blocks/scaffold/scaffold-actions.d.ts +5 -4
  10. package/dist-lib/components/blocks/scaffold/scaffold-canvas.d.ts +2 -0
  11. package/dist-lib/components/blocks/scaffold/scaffold-panel.d.ts +1 -1
  12. package/dist-lib/components/blocks/scaffold/scaffold-tab.d.ts +14 -0
  13. package/dist-lib/components/blocks/scaffold/type.d.ts +58 -28
  14. package/dist-lib/components/blocks/scaffold/use-panel-visibility.d.ts +19 -0
  15. package/dist-lib/components/blocks/scaffold/utils.d.ts +12 -0
  16. package/dist-lib/components/composed/tree/__tests__/tree.test.d.ts +1 -0
  17. package/dist-lib/components/composed/tree/__tests__/use-tree.test.d.ts +1 -0
  18. package/dist-lib/components/composed/tree/constants.d.ts +35 -0
  19. package/dist-lib/components/composed/tree/index.d.ts +4 -0
  20. package/dist-lib/components/composed/tree/tree-context.d.ts +4 -0
  21. package/dist-lib/components/composed/tree/tree-node.d.ts +7 -0
  22. package/dist-lib/components/composed/tree/tree.d.ts +19 -0
  23. package/dist-lib/components/composed/tree/type.d.ts +192 -0
  24. package/dist-lib/components/composed/tree/use-tree-dnd.d.ts +7 -0
  25. package/dist-lib/components/composed/tree/use-tree.d.ts +20 -0
  26. package/dist-lib/components/composed/tree/utils.d.ts +28 -0
  27. package/dist-lib/components/user-avatar/audio-wave.d.ts +2 -0
  28. package/dist-lib/components/user-avatar/component.d.ts +10 -0
  29. package/dist-lib/components/user-avatar/fallback.d.ts +5 -0
  30. package/dist-lib/components/user-avatar/index.d.ts +13 -0
  31. package/dist-lib/components/user-avatar/type.d.ts +26 -0
  32. package/dist-lib/components/user-avatar/utils.d.ts +2 -0
  33. package/dist-lib/index.d.ts +1 -0
  34. package/dist-lib/index.js +2309 -1285
  35. package/dist-lib/index.js.map +1 -1
  36. package/package.json +2 -1
  37. package/src/components/beste/piece/browser-frame.tsx +9 -6
  38. package/src/components/blocks/nav/sidebar/__tests__/use-ai-sidebar.test.tsx +310 -0
  39. package/src/components/blocks/nav/sidebar/ai-sidebar.tsx +90 -60
  40. package/src/components/blocks/nav/sidebar/type.ts +105 -1
  41. package/src/components/blocks/nav/sidebar/use-ai-sidebar.ts +293 -116
  42. package/src/components/blocks/nav/sidebar/utils.ts +28 -0
  43. package/src/components/blocks/scaffold/__tests__/scaffold.test.tsx +268 -26
  44. package/src/components/blocks/scaffold/constants.ts +11 -0
  45. package/src/components/blocks/scaffold/index.ts +4 -1
  46. package/src/components/blocks/scaffold/panel-menu-button.tsx +96 -115
  47. package/src/components/blocks/scaffold/scaffold-actions.tsx +41 -50
  48. package/src/components/blocks/scaffold/scaffold-canvas.tsx +48 -5
  49. package/src/components/blocks/scaffold/scaffold-inspector.tsx +27 -3
  50. package/src/components/blocks/scaffold/scaffold-main.tsx +1 -1
  51. package/src/components/blocks/scaffold/scaffold-panel.tsx +56 -30
  52. package/src/components/blocks/scaffold/scaffold-root.tsx +18 -3
  53. package/src/components/blocks/scaffold/scaffold-sidebar.tsx +1 -1
  54. package/src/components/blocks/scaffold/scaffold-tab.tsx +104 -0
  55. package/src/components/blocks/scaffold/type.ts +62 -33
  56. package/src/components/blocks/scaffold/use-panel-visibility.ts +180 -0
  57. package/src/components/blocks/scaffold/utils.ts +35 -0
  58. package/src/components/composed/tree/__tests__/tree.test.tsx +400 -0
  59. package/src/components/composed/tree/__tests__/use-tree.test.tsx +332 -0
  60. package/src/components/composed/tree/constants.ts +60 -0
  61. package/src/components/composed/tree/index.ts +13 -0
  62. package/src/components/composed/tree/tree-context.ts +13 -0
  63. package/src/components/composed/tree/tree-node.tsx +451 -0
  64. package/src/components/composed/tree/tree.tsx +278 -0
  65. package/src/components/composed/tree/type.ts +212 -0
  66. package/src/components/composed/tree/use-tree-dnd.ts +148 -0
  67. package/src/components/composed/tree/use-tree.ts +306 -0
  68. package/src/components/composed/tree/utils.ts +172 -0
  69. package/src/components/composed/user-avatar.tsx +4 -1
  70. package/src/components/docs/component-preview.tsx +11 -11
  71. package/src/components/user-avatar/audio-wave.tsx +22 -0
  72. package/src/components/user-avatar/component.tsx +53 -0
  73. package/src/components/user-avatar/fallback.tsx +26 -0
  74. package/src/components/user-avatar/index.ts +15 -0
  75. package/src/components/user-avatar/type.ts +35 -0
  76. package/src/components/user-avatar/utils.ts +8 -0
  77. package/src/index.css +211 -153
  78. package/src/index.ts +1 -0
@@ -1,25 +1,48 @@
1
1
  import { describe, expect, mock, test } from "bun:test"
2
- import { render, screen, waitFor } from "@testing-library/react"
2
+ import { act, render, screen, waitFor } from "@testing-library/react"
3
3
  import userEvent from "@testing-library/user-event"
4
+ import type { ReactNode } from "react"
4
5
 
5
6
  import { Scaffold, useScaffold } from "../index"
6
7
  import type { TScaffoldPanelChild } from "../type"
8
+ import { getPanelCapacity } from "../utils"
9
+
10
+ // The canvas measures itself through ResizeObserver; this stub hands the
11
+ // callback to the test so it can drive the width deterministically.
12
+ let observeCanvas: ResizeObserverCallback | undefined
13
+ globalThis.ResizeObserver = class {
14
+ constructor(callback: ResizeObserverCallback) {
15
+ observeCanvas = callback
16
+ }
17
+ observe() {}
18
+ unobserve() {}
19
+ disconnect() {}
20
+ } as unknown as typeof ResizeObserver
21
+
22
+ const resizeCanvas = (width: number) =>
23
+ act(() => {
24
+ observeCanvas?.(
25
+ [{ contentRect: { width } }] as unknown as ResizeObserverEntry[],
26
+ undefined as unknown as ResizeObserver
27
+ )
28
+ })
7
29
 
8
30
  function Strip() {
9
31
  return <span>Strip</span>
10
32
  }
11
33
 
12
34
  function Workspace({
13
- onClose,
14
35
  onSwap,
36
+ onCloseSecondary,
15
37
  onAdd,
16
- overflowCount,
38
+ tabs,
17
39
  SecondaryPanel = <Strip />,
18
40
  }: {
19
- onClose?: () => void
20
41
  onSwap?: () => void
42
+ onCloseSecondary?: () => void
21
43
  onAdd?: () => void
22
- overflowCount?: number
44
+ /** Rendered as Actions children — the panel tabs. */
45
+ tabs?: ReactNode
23
46
  /** Pass null to render the panel without a secondary strip. */
24
47
  SecondaryPanel?: TScaffoldPanelChild | null
25
48
  }) {
@@ -33,12 +56,12 @@ function Workspace({
33
56
  </button>
34
57
  </Scaffold.Sidebar>
35
58
  <Scaffold.Main>
36
- <Scaffold.Actions onAdd={onAdd} overflowCount={overflowCount} />
59
+ <Scaffold.Actions onAdd={onAdd}>{tabs}</Scaffold.Actions>
37
60
  <Scaffold.Canvas>
38
61
  <Scaffold.Panel
39
62
  key="alpha"
40
63
  name="Alpha"
41
- onClose={onClose}
64
+ onCloseSecondary={onCloseSecondary}
42
65
  onSwap={onSwap}
43
66
  SecondaryPanel={SecondaryPanel ?? undefined}
44
67
  >
@@ -57,6 +80,12 @@ function Workspace({
57
80
  const menuToggle = () =>
58
81
  screen.queryByRole("button", { name: "Swap panel content" })
59
82
 
83
+ /** Under the id'd `#scaffold-actions` ancestor, happy-dom serves stale
84
+ * selector-cache results to `waitFor` queries that start while an
85
+ * AnimatePresence exit is still running — let the exit settle on real
86
+ * timers, then query once. */
87
+ const settleExit = () => new Promise((resolve) => setTimeout(resolve, 400))
88
+
60
89
  describe("Scaffold", () => {
61
90
  test("renders rail, panel surfaces and secondary strip", () => {
62
91
  render(<Workspace />)
@@ -67,18 +96,38 @@ describe("Scaffold", () => {
67
96
  expect(panel.textContent).toContain("Strip")
68
97
  })
69
98
 
70
- test("close and menu buttons render only with their callbacks", async () => {
71
- const user = userEvent.setup()
72
- const onClose = mock(() => {})
73
-
99
+ test("seam menu renders only with a callback; actions stay hidden until opened", () => {
74
100
  const { rerender } = render(<Workspace />)
75
- expect(screen.queryByRole("button", { name: "Close panel" })).toBeNull()
76
101
  expect(menuToggle()).toBeNull()
77
102
 
78
- rerender(<Workspace onClose={onClose} onSwap={() => {}} />)
79
- await user.click(screen.getByRole("button", { name: "Close panel" }))
80
- expect(onClose).toHaveBeenCalledTimes(1)
103
+ rerender(<Workspace onSwap={() => {}} />)
104
+ expect(menuToggle()).toBeDefined()
105
+ // Collapsed: the action segment is aria-hidden and inert.
106
+ expect(screen.queryByRole("button", { name: "Swap" })).toBeNull()
107
+
108
+ // The Close action alone also earns the menu.
109
+ rerender(<Workspace onCloseSecondary={() => {}} />)
110
+ expect(menuToggle()).toBeDefined()
111
+ })
112
+
113
+ test("seam menu Close renders only with onCloseSecondary, fires and collapses", async () => {
114
+ const user = userEvent.setup()
115
+ const onCloseSecondary = mock(() => {})
116
+
117
+ const { rerender } = render(<Workspace onSwap={() => {}} />)
118
+ await user.click(menuToggle() as HTMLElement)
119
+ expect(await screen.findByRole("button", { name: "Swap" })).toBeDefined()
120
+ expect(screen.queryByRole("button", { name: "Close" })).toBeNull()
121
+
122
+ rerender(
123
+ <Workspace onCloseSecondary={onCloseSecondary} onSwap={() => {}} />
124
+ )
125
+ await user.click(screen.getByRole("button", { name: "Close" }))
126
+ expect(onCloseSecondary).toHaveBeenCalledTimes(1)
127
+
128
+ // Acting collapses the menu back to the compact toggle.
81
129
  expect(menuToggle()).toBeDefined()
130
+ expect(screen.queryByRole("button", { name: "Close" })).toBeNull()
82
131
  })
83
132
 
84
133
  test("seam menu opens; Swap fires onSwap, flips the panel and collapses the menu", async () => {
@@ -140,31 +189,86 @@ describe("Scaffold", () => {
140
189
  expect(bottom.getAttribute("data-expanded")).toBeNull()
141
190
  })
142
191
 
192
+ test("expand chevron mirrors when the panel is swapped", async () => {
193
+ const user = userEvent.setup()
194
+ render(<Workspace onSwap={() => {}} />)
195
+
196
+ // Swap first — column-reverse puts the strip visually on top.
197
+ await user.click(menuToggle() as HTMLElement)
198
+ await user.click(await screen.findByRole("button", { name: "Swap" }))
199
+
200
+ // Primary still holds the flexible slot, but the strip that grows
201
+ // next now sits on top — the chevron points up.
202
+ await user.click(
203
+ await screen.findByRole("button", { name: "Swap panel content" })
204
+ )
205
+ const expandButton = await screen.findByRole("button", { name: "Expand" })
206
+ const chevron = () => expandButton.querySelector("svg.lucide-chevron-down")
207
+ expect(chevron()?.classList.contains("rotate-180")).toBe(true)
208
+
209
+ // After expanding, the primary — now visually at the bottom — grows
210
+ // next: the chevron points down again.
211
+ await user.click(expandButton)
212
+ await user.click(
213
+ await screen.findByRole("button", { name: "Swap panel content" })
214
+ )
215
+ expect(chevron()?.classList.contains("rotate-180")).toBe(false)
216
+ })
217
+
143
218
  test("seam menu needs a secondary strip", () => {
144
219
  render(<Workspace onSwap={() => {}} SecondaryPanel={null} />)
145
220
 
146
221
  expect(menuToggle()).toBeNull()
147
222
  })
148
223
 
149
- test("actions cluster: Add fires, overflow segment follows the count", async () => {
224
+ test("actions cluster: Add renders only with onAdd and fires", async () => {
150
225
  const user = userEvent.setup()
151
226
  const onAdd = mock(() => {})
152
227
 
153
- const { rerender } = render(<Workspace onAdd={onAdd} />)
154
- expect(screen.queryByRole("button", { name: "Browse panels" })).toBeNull()
228
+ // Consumers omit onAdd at SCAFFOLD_PANEL_CAPACITY — the segment hides.
229
+ const { rerender } = render(<Workspace />)
230
+ expect(screen.queryByRole("button", { name: "Panel" })).toBeNull()
155
231
 
232
+ rerender(<Workspace onAdd={onAdd} />)
156
233
  await user.click(screen.getByRole("button", { name: "Panel" }))
157
234
  expect(onAdd).toHaveBeenCalledTimes(1)
158
235
 
159
- // Zero overflowed panels: the segment shows without a badge.
160
- rerender(<Workspace overflowCount={0} onAdd={onAdd} />)
161
- expect(
162
- screen.getByRole("button", { name: "Browse panels" }).textContent
163
- ).toBe("")
236
+ rerender(<Workspace />)
237
+ await settleExit()
238
+ expect(screen.queryByRole("button", { name: "Panel" })).toBeNull()
239
+ })
240
+
241
+ test("panel tabs show their label; close renders only with onClose and fires", async () => {
242
+ const user = userEvent.setup()
243
+ const onCloseTab = mock(() => {})
244
+
245
+ // Consumers omit onClose on the last remaining panel's tab.
246
+ const { rerender } = render(
247
+ <Workspace tabs={<Scaffold.Tab key="alpha">Alpha</Scaffold.Tab>} />
248
+ )
249
+ expect(screen.getByText("Alpha")).toBeDefined()
250
+ expect(screen.queryByRole("button", { name: "Close Alpha" })).toBeNull()
251
+
252
+ rerender(
253
+ <Workspace
254
+ tabs={
255
+ <Scaffold.Tab
256
+ key="alpha"
257
+ closeLabel="Close Alpha"
258
+ onClose={onCloseTab}
259
+ >
260
+ Alpha
261
+ </Scaffold.Tab>
262
+ }
263
+ />
264
+ )
265
+ await user.click(screen.getByRole("button", { name: "Close Alpha" }))
266
+ expect(onCloseTab).toHaveBeenCalledTimes(1)
164
267
 
165
- rerender(<Workspace overflowCount={2} onAdd={onAdd} />)
166
- const browse = screen.getByRole("button", { name: "Browse panels" })
167
- expect(browse.textContent).toContain("2")
268
+ // Closing removes the tab — it animates out of the pill.
269
+ rerender(<Workspace tabs={null} />)
270
+ await settleExit()
271
+ expect(screen.queryByText("Alpha")).toBeNull()
168
272
  })
169
273
 
170
274
  test("inspector opens from outside state and hides again", async () => {
@@ -185,6 +289,16 @@ describe("Scaffold", () => {
185
289
  expect(inspector().getAttribute("aria-hidden")).toBe("true")
186
290
  })
187
291
 
292
+ test("capacity grants each panel 320px plus the gaps between them", () => {
293
+ expect(getPanelCapacity(null)).toBe(3)
294
+ expect(getPanelCapacity(976)).toBe(3) // 3×320 + 2×8
295
+ expect(getPanelCapacity(975)).toBe(2)
296
+ expect(getPanelCapacity(648)).toBe(2) // 2×320 + 8
297
+ expect(getPanelCapacity(647)).toBe(1)
298
+ expect(getPanelCapacity(100)).toBe(1) // never below one panel
299
+ expect(getPanelCapacity(5000)).toBe(3) // never above the cap
300
+ })
301
+
188
302
  test("root manages inspector state itself when uncontrolled", () => {
189
303
  render(
190
304
  <Scaffold.Root defaultInspectorOpen>
@@ -201,3 +315,131 @@ describe("Scaffold", () => {
201
315
  ).toBeDefined()
202
316
  })
203
317
  })
318
+
319
+ const PANEL_IDS = ["one", "two", "three"] as const
320
+
321
+ function ResponsiveWorkspace() {
322
+ return (
323
+ <Scaffold.Root>
324
+ <Scaffold.Main>
325
+ <Scaffold.Actions>
326
+ {PANEL_IDS.map((id) => (
327
+ <Scaffold.Tab key={id} panelId={id}>
328
+ {`Tab ${id}`}
329
+ </Scaffold.Tab>
330
+ ))}
331
+ </Scaffold.Actions>
332
+ <Scaffold.Canvas>
333
+ {PANEL_IDS.map((id) => (
334
+ <Scaffold.Panel key={id} id={id} name={`Panel ${id}`}>
335
+ <span>{`Body ${id}`}</span>
336
+ </Scaffold.Panel>
337
+ ))}
338
+ </Scaffold.Canvas>
339
+ </Scaffold.Main>
340
+ </Scaffold.Root>
341
+ )
342
+ }
343
+
344
+ const panel = (id: string) =>
345
+ screen.queryByRole("region", { name: `Panel ${id}` })
346
+
347
+ const tab = (id: string) => screen.getByRole("button", { name: `Tab ${id}` })
348
+
349
+ describe("Scaffold responsive panels", () => {
350
+ test("narrowing hides the oldest panel; widening restores it", async () => {
351
+ render(<ResponsiveWorkspace />)
352
+
353
+ resizeCanvas(976)
354
+ for (const id of PANEL_IDS) expect(panel(id)).not.toBeNull()
355
+
356
+ // Two panels' worth of room — the least-recently-activated hides.
357
+ resizeCanvas(700)
358
+ await settleExit()
359
+ expect(panel("one")).toBeNull()
360
+ expect(panel("two")).not.toBeNull()
361
+ expect(panel("three")).not.toBeNull()
362
+ expect(
363
+ tab("one")
364
+ .closest('[data-slot="scaffold-tab"]')
365
+ ?.getAttribute("data-panel-hidden")
366
+ ).toBe("")
367
+ expect(tab("one").getAttribute("aria-pressed")).toBe("false")
368
+
369
+ // Room returns — the auto-hidden panel comes back by itself.
370
+ resizeCanvas(1200)
371
+ expect(await screen.findByRole("region", { name: "Panel one" })).toBeDefined()
372
+ expect(tab("one").getAttribute("aria-pressed")).toBe("true")
373
+ })
374
+
375
+ test("pressing a hidden tab shows its panel, hiding the oldest visible one", async () => {
376
+ const user = userEvent.setup()
377
+ render(<ResponsiveWorkspace />)
378
+
379
+ resizeCanvas(700)
380
+ await settleExit()
381
+ expect(panel("one")).toBeNull()
382
+
383
+ await user.click(tab("one"))
384
+ await settleExit()
385
+ expect(panel("one")).not.toBeNull()
386
+ expect(panel("two")).toBeNull()
387
+ expect(panel("three")).not.toBeNull()
388
+ })
389
+
390
+ test("pressing a visible tab hides its panel; the last one never hides", async () => {
391
+ const user = userEvent.setup()
392
+ render(<ResponsiveWorkspace />)
393
+
394
+ resizeCanvas(400)
395
+ await settleExit()
396
+ // One panel's worth of room — only the newest activation stays.
397
+ expect(panel("one")).toBeNull()
398
+ expect(panel("two")).toBeNull()
399
+ expect(panel("three")).not.toBeNull()
400
+
401
+ // The sole visible panel refuses to hide.
402
+ await user.click(tab("three"))
403
+ await settleExit()
404
+ expect(panel("three")).not.toBeNull()
405
+
406
+ // Activating another trades places with it.
407
+ await user.click(tab("one"))
408
+ await settleExit()
409
+ expect(panel("one")).not.toBeNull()
410
+ expect(panel("three")).toBeNull()
411
+ })
412
+
413
+ test("hovering a tab spotlights its panel and dims the others", async () => {
414
+ const user = userEvent.setup()
415
+ render(<ResponsiveWorkspace />)
416
+
417
+ resizeCanvas(1200)
418
+ await user.hover(tab("two"))
419
+ expect(panel("two")?.getAttribute("data-dimmed")).toBeNull()
420
+ expect(panel("one")?.getAttribute("data-dimmed")).toBe("")
421
+ expect(panel("three")?.getAttribute("data-dimmed")).toBe("")
422
+
423
+ await user.unhover(tab("two"))
424
+ expect(panel("one")?.getAttribute("data-dimmed")).toBeNull()
425
+ expect(panel("three")?.getAttribute("data-dimmed")).toBeNull()
426
+ })
427
+
428
+ test("user-hidden panels stay hidden when the canvas widens", async () => {
429
+ const user = userEvent.setup()
430
+ render(<ResponsiveWorkspace />)
431
+
432
+ resizeCanvas(1200)
433
+ await user.click(tab("two"))
434
+ await settleExit()
435
+ expect(panel("two")).toBeNull()
436
+
437
+ resizeCanvas(1400)
438
+ await settleExit()
439
+ expect(panel("two")).toBeNull()
440
+
441
+ // Its tab presses back to visible.
442
+ await user.click(tab("two"))
443
+ expect(await screen.findByRole("region", { name: "Panel two" })).toBeDefined()
444
+ })
445
+ })
@@ -4,6 +4,17 @@ export const SCAFFOLD_SIDEBAR_WIDTH = 64
4
4
  /** Inspector drawer width in px. */
5
5
  export const SCAFFOLD_INSPECTOR_WIDTH = 272
6
6
 
7
+ /** Most panels the canvas holds at once — omit Actions' `onAdd` when the
8
+ * count reaches it so the Add segment hides. */
9
+ export const SCAFFOLD_PANEL_CAPACITY = 3
10
+
11
+ /** Narrowest a panel may render, in px. When the canvas can't grant every
12
+ * panel this width, the scaffold hides panels until the rest fit. */
13
+ export const SCAFFOLD_PANEL_MIN_WIDTH = 320
14
+
15
+ /** Gap in px between side-by-side panels on the canvas (`gap-2`). */
16
+ export const SCAFFOLD_CANVAS_GAP = 8
17
+
7
18
  /** Gap in px between the drawer/actions and the viewport edges. */
8
19
  export const SCAFFOLD_EDGE_GUTTER = 10
9
20
 
@@ -7,6 +7,7 @@ import { ScaffoldMain } from "./scaffold-main"
7
7
  import { ScaffoldPanel } from "./scaffold-panel"
8
8
  import { ScaffoldRoot } from "./scaffold-root"
9
9
  import { ScaffoldSidebar } from "./scaffold-sidebar"
10
+ import { ScaffoldTab } from "./scaffold-tab"
10
11
  import { ScaffoldSubPanel } from "@/components/blocks/scaffold/scaffold-sub-panel.tsx"
11
12
 
12
13
  export { useScaffoldContext } from "./scaffold-context"
@@ -24,6 +25,7 @@ export {
24
25
  ScaffoldPanel,
25
26
  ScaffoldRoot,
26
27
  ScaffoldSidebar,
28
+ ScaffoldTab,
27
29
  }
28
30
 
29
31
  export const Scaffold = {
@@ -33,6 +35,7 @@ export const Scaffold = {
33
35
  Actions: ScaffoldActions,
34
36
  Canvas: ScaffoldCanvas,
35
37
  Panel: ScaffoldPanel,
38
+ Tab: ScaffoldTab,
36
39
  Inspector: ScaffoldInspector,
37
- SubPanel: ScaffoldSubPanel
40
+ SubPanel: ScaffoldSubPanel,
38
41
  }