@dfosco/storyboard-core 3.3.2 → 3.5.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 (51) hide show
  1. package/dist/storyboard-ui.css +1 -1
  2. package/dist/storyboard-ui.js +14899 -11508
  3. package/dist/storyboard-ui.js.map +1 -1
  4. package/dist/tailwind.css +1 -1
  5. package/package.json +1 -1
  6. package/scaffold/toolbar.config.json +2 -2
  7. package/src/CanvasCreateMenu.svelte +1 -1
  8. package/src/CanvasZoomControl.svelte +105 -0
  9. package/src/CommandMenu.svelte +87 -25
  10. package/src/CoreUIBar.svelte +350 -347
  11. package/src/CreateMenuButton.svelte +6 -2
  12. package/src/InspectorPanel.svelte +123 -59
  13. package/src/SidePanel.svelte +1 -1
  14. package/src/ThemeMenuButton.svelte +35 -3
  15. package/src/commandActions.js +14 -0
  16. package/src/core-ui-colors.css +30 -2
  17. package/src/devtools.js +7 -1
  18. package/src/index.js +10 -1
  19. package/src/inspector/fiberWalker.js +49 -6
  20. package/src/inspector/highlighter.js +257 -33
  21. package/src/lib/components/ui/button/button.svelte +1 -1
  22. package/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte +1 -1
  23. package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +1 -1
  24. package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +1 -1
  25. package/src/lib/components/ui/trigger-button/trigger-button.svelte +31 -3
  26. package/src/modes.css +8 -0
  27. package/src/mountStoryboardCore.js +15 -1
  28. package/src/sidepanel.css +2 -2
  29. package/src/stores/themeStore.ts +66 -0
  30. package/src/svelte-plugin-ui/components/Viewfinder.svelte +16 -11
  31. package/src/toolRegistry.js +226 -0
  32. package/src/toolStateStore.js +180 -0
  33. package/src/toolStateStore.test.js +204 -0
  34. package/src/toolbarConfigStore.js +135 -0
  35. package/src/tools/handlers/canvasAddWidget.js +11 -0
  36. package/src/tools/handlers/canvasZoom.js +34 -0
  37. package/src/tools/handlers/comments.js +16 -0
  38. package/src/tools/handlers/create.js +39 -0
  39. package/src/tools/handlers/devtools.js +80 -0
  40. package/src/tools/handlers/docs.js +11 -0
  41. package/src/tools/handlers/featureFlags.js +21 -0
  42. package/src/tools/handlers/flows.js +62 -0
  43. package/src/tools/handlers/inspector.js +19 -0
  44. package/src/tools/handlers/theme.js +9 -0
  45. package/src/tools/registry.js +21 -0
  46. package/src/tools/surfaces/canvasToolbar.js +10 -0
  47. package/src/tools/surfaces/commandList.js +10 -0
  48. package/src/tools/surfaces/mainToolbar.js +11 -0
  49. package/src/tools/surfaces/registry.js +19 -0
  50. package/src/vite/server-plugin.js +36 -6
  51. package/toolbar.config.json +101 -48
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Tool Registry — config-driven state management for toolbar tools.
3
+ *
4
+ * Every tool is declared in toolbar.config.json under the `tools` key.
5
+ * Each tool specifies a `toolbar` target (main-toolbar, secondary-toolbar,
6
+ * command-list) and a `render` type (button, menu, sidepanel, submenu, link).
7
+ *
8
+ * Code modules register themselves via registerToolModule() to provide
9
+ * component, handler, setup, and guard functions.
10
+ *
11
+ * Framework-agnostic (zero npm dependencies).
12
+ */
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Internal state
16
+ // ---------------------------------------------------------------------------
17
+
18
+ /** @type {Record<string, object>} tool id → config from toolbar.config.json */
19
+ let _toolConfigs = {}
20
+
21
+ /** @type {Map<string, object>} tool id → code module { component?, handler?, setup?, guard? } */
22
+ const _modules = new Map()
23
+
24
+ /** @type {Map<string, any>} tool id → resolved component (after lazy loading) */
25
+ const _components = new Map()
26
+
27
+ /** @type {Map<string, boolean>} tool id → guard result */
28
+ const _guardResults = new Map()
29
+
30
+ /** @type {Set<Function>} */
31
+ const _listeners = new Set()
32
+
33
+ let _snapshotVersion = 0
34
+
35
+ // ---------------------------------------------------------------------------
36
+ // Initialization
37
+ // ---------------------------------------------------------------------------
38
+
39
+ /**
40
+ * Seed the registry from toolbar config.
41
+ * Called once at app startup.
42
+ *
43
+ * @param {object} config - The full toolbar config object
44
+ */
45
+ export function initToolRegistry(config) {
46
+ if (config.tools) {
47
+ _toolConfigs = { ...config.tools }
48
+ }
49
+ _notify()
50
+ }
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // Module registration
54
+ // ---------------------------------------------------------------------------
55
+
56
+ /**
57
+ * Register a code module for a declared tool.
58
+ *
59
+ * @param {string} id - Tool id (matches key in toolbar.config.json tools)
60
+ * @param {object} mod
61
+ * @param {Function} [mod.component] - () => import('./SomeComponent.svelte')
62
+ * @param {object|Function} [mod.handler] - Command action handler
63
+ * @param {Function} [mod.setup] - async (ctx) => void — called once at mount
64
+ * @param {Function} [mod.guard] - async (ctx) => boolean — return false to hide
65
+ */
66
+ export function registerToolModule(id, mod) {
67
+ _modules.set(id, mod)
68
+ _notify()
69
+ }
70
+
71
+ /**
72
+ * Store a resolved component for a tool (after lazy loading).
73
+ *
74
+ * @param {string} id
75
+ * @param {any} component
76
+ */
77
+ export function setToolComponent(id, component) {
78
+ _components.set(id, component)
79
+ _notify()
80
+ }
81
+
82
+ /**
83
+ * Store a guard result for a tool.
84
+ *
85
+ * @param {string} id
86
+ * @param {boolean} result
87
+ */
88
+ export function setToolGuardResult(id, result) {
89
+ _guardResults.set(id, result)
90
+ _notify()
91
+ }
92
+
93
+ /**
94
+ * Get the resolved component for a tool.
95
+ *
96
+ * @param {string} id
97
+ * @returns {any|null}
98
+ */
99
+ export function getToolComponent(id) {
100
+ return _components.get(id) || null
101
+ }
102
+
103
+ /**
104
+ * Get the code module for a tool.
105
+ *
106
+ * @param {string} id
107
+ * @returns {object|null}
108
+ */
109
+ export function getToolModule(id) {
110
+ return _modules.get(id) || null
111
+ }
112
+
113
+ // ---------------------------------------------------------------------------
114
+ // Resolution
115
+ // ---------------------------------------------------------------------------
116
+
117
+ /**
118
+ * Get tools for a specific toolbar target, filtered by mode and visibility.
119
+ *
120
+ * @param {string} toolbar - "main-toolbar" | "secondary-toolbar" | "command-list"
121
+ * @param {string} mode - Current mode name
122
+ * @param {object} [options]
123
+ * @param {boolean} [options.isLocalDev] - Whether running in local dev
124
+ * @returns {Array<{ id: string, config: object, module: object|null, component: any|null }>}
125
+ */
126
+ export function getToolsForToolbar(toolbar, mode, options = {}) {
127
+ const { isLocalDev = false } = options
128
+ const results = []
129
+
130
+ for (const [id, config] of Object.entries(_toolConfigs)) {
131
+ if (config.toolbar !== toolbar) continue
132
+ if (config.localOnly && !isLocalDev) continue
133
+ if (!isToolVisibleInMode(config, mode)) continue
134
+
135
+ // Check guard result if one was registered
136
+ if (_guardResults.has(id) && !_guardResults.get(id)) continue
137
+
138
+ results.push({
139
+ id,
140
+ config,
141
+ module: _modules.get(id) || null,
142
+ component: _components.get(id) || null,
143
+ })
144
+ }
145
+
146
+ return results
147
+ }
148
+
149
+ /**
150
+ * Get the config for a specific tool.
151
+ *
152
+ * @param {string} id
153
+ * @returns {object|null}
154
+ */
155
+ export function getToolConfig(id) {
156
+ return _toolConfigs[id] || null
157
+ }
158
+
159
+ /**
160
+ * Get all tool configs.
161
+ *
162
+ * @returns {Record<string, object>}
163
+ */
164
+ export function getAllToolConfigs() {
165
+ return { ..._toolConfigs }
166
+ }
167
+
168
+ /**
169
+ * Check if a tool is visible in a given mode.
170
+ *
171
+ * @param {object} config - Tool config
172
+ * @param {string} mode - Current mode name
173
+ * @returns {boolean}
174
+ */
175
+ function isToolVisibleInMode(config, mode) {
176
+ const modes = config.modes
177
+ if (!modes) return true
178
+ return modes.includes('*') || modes.includes(mode)
179
+ }
180
+
181
+ // ---------------------------------------------------------------------------
182
+ // Reactivity
183
+ // ---------------------------------------------------------------------------
184
+
185
+ /**
186
+ * Subscribe to registry changes. Compatible with useSyncExternalStore.
187
+ *
188
+ * @param {Function} callback
189
+ * @returns {Function} Unsubscribe
190
+ */
191
+ export function subscribeToToolRegistry(callback) {
192
+ _listeners.add(callback)
193
+ return () => _listeners.delete(callback)
194
+ }
195
+
196
+ /**
197
+ * Snapshot for useSyncExternalStore.
198
+ *
199
+ * @returns {string}
200
+ */
201
+ export function getToolRegistrySnapshot() {
202
+ return String(_snapshotVersion)
203
+ }
204
+
205
+ function _notify() {
206
+ _snapshotVersion++
207
+ for (const cb of _listeners) {
208
+ try { cb() } catch (err) {
209
+ console.error('[storyboard] Error in tool registry subscriber:', err)
210
+ }
211
+ }
212
+ }
213
+
214
+ // ---------------------------------------------------------------------------
215
+ // Test helpers
216
+ // ---------------------------------------------------------------------------
217
+
218
+ /** Reset all state. Only for tests. */
219
+ export function _resetToolRegistry() {
220
+ _toolConfigs = {}
221
+ _modules.clear()
222
+ _components.clear()
223
+ _guardResults.clear()
224
+ _listeners.clear()
225
+ _snapshotVersion = 0
226
+ }
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Tool State Store — runtime state management for toolbar tools.
3
+ *
4
+ * Each tool declared in toolbar.config.json can be in one of five states:
5
+ * active — Normal, clickable (default)
6
+ * inactive — Visible but unclickable, disabled-looking (also for errors)
7
+ * hidden — Invisible but shortcuts still work, tool is loaded
8
+ * dimmed — Visible but dimmed opacity, still clickable
9
+ * disabled — Completely removed, not loaded on FE
10
+ *
11
+ * Tools default to "active" unless:
12
+ * 1. Config declares a "state" property
13
+ * 2. The tool is localOnly and the environment is not local dev → "disabled"
14
+ *
15
+ * State can be changed at runtime by application code via setToolbarToolState().
16
+ * Framework-agnostic (zero npm dependencies).
17
+ */
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Constants
21
+ // ---------------------------------------------------------------------------
22
+
23
+ /** All valid tool states. */
24
+ export const TOOL_STATES = Object.freeze({
25
+ ACTIVE: 'active',
26
+ INACTIVE: 'inactive',
27
+ HIDDEN: 'hidden',
28
+ DIMMED: 'dimmed',
29
+ DISABLED: 'disabled',
30
+ })
31
+
32
+ const VALID_STATES = new Set(Object.values(TOOL_STATES))
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Internal state
36
+ // ---------------------------------------------------------------------------
37
+
38
+ /** @type {Map<string, string>} tool id → state */
39
+ let _states = new Map()
40
+
41
+ /** @type {Map<string, boolean>} tool id → localOnly flag */
42
+ let _localOnlyFlags = new Map()
43
+
44
+ /** @type {Set<Function>} */
45
+ const _listeners = new Set()
46
+
47
+ let _snapshotVersion = 0
48
+
49
+ // ---------------------------------------------------------------------------
50
+ // Initialization
51
+ // ---------------------------------------------------------------------------
52
+
53
+ /**
54
+ * Seed tool states from toolbar.config.json's `tools` object.
55
+ * Called once at app startup.
56
+ *
57
+ * @param {object} toolsConfig - The `tools` object from toolbar.config.json
58
+ * (keys are tool IDs, values are tool config objects)
59
+ * @param {{ isLocalDev?: boolean }} [options]
60
+ */
61
+ export function initToolbarToolStates(toolsConfig, options = {}) {
62
+ const { isLocalDev = false } = options
63
+
64
+ _states = new Map()
65
+ _localOnlyFlags = new Map()
66
+
67
+ if (!toolsConfig || typeof toolsConfig !== 'object') {
68
+ _notify()
69
+ return
70
+ }
71
+
72
+ for (const [id, tool] of Object.entries(toolsConfig)) {
73
+ const isLocalOnly = tool.localOnly === true
74
+ _localOnlyFlags.set(id, isLocalOnly)
75
+
76
+ if (isLocalOnly && !isLocalDev) {
77
+ _states.set(id, TOOL_STATES.DISABLED)
78
+ } else {
79
+ const state = tool.state || TOOL_STATES.ACTIVE
80
+ _states.set(id, state)
81
+ }
82
+ }
83
+
84
+ _notify()
85
+ }
86
+
87
+ // ---------------------------------------------------------------------------
88
+ // Runtime state changes
89
+ // ---------------------------------------------------------------------------
90
+
91
+ /**
92
+ * Set state for a tool at runtime.
93
+ * Validates that the state is one of the 5 valid values.
94
+ *
95
+ * @param {string} id Tool id
96
+ * @param {string} state One of: active, inactive, hidden, dimmed, disabled
97
+ */
98
+ export function setToolbarToolState(id, state) {
99
+ if (!VALID_STATES.has(state)) {
100
+ console.warn(`[storyboard] Invalid tool state "${state}" for tool "${id}". Valid states: ${[...VALID_STATES].join(', ')}`)
101
+ return
102
+ }
103
+ _states.set(id, state)
104
+ _notify()
105
+ }
106
+
107
+ // ---------------------------------------------------------------------------
108
+ // Access
109
+ // ---------------------------------------------------------------------------
110
+
111
+ /**
112
+ * Get the current state for a tool.
113
+ * Returns "active" for unknown tool IDs (safe default).
114
+ *
115
+ * @param {string} id Tool id
116
+ * @returns {string}
117
+ */
118
+ export function getToolbarToolState(id) {
119
+ return _states.get(id) || TOOL_STATES.ACTIVE
120
+ }
121
+
122
+ /**
123
+ * Returns whether the tool was marked localOnly in config.
124
+ * Returns false for unknown IDs.
125
+ *
126
+ * @param {string} id Tool id
127
+ * @returns {boolean}
128
+ */
129
+ export function isToolbarToolLocalOnly(id) {
130
+ return _localOnlyFlags.get(id) || false
131
+ }
132
+
133
+ // ---------------------------------------------------------------------------
134
+ // Reactivity
135
+ // ---------------------------------------------------------------------------
136
+
137
+ /**
138
+ * Subscribe to tool state changes. Compatible with useSyncExternalStore.
139
+ *
140
+ * @param {Function} callback
141
+ * @returns {Function} Unsubscribe
142
+ */
143
+ export function subscribeToToolbarToolStates(callback) {
144
+ _listeners.add(callback)
145
+ return () => _listeners.delete(callback)
146
+ }
147
+
148
+ /**
149
+ * Snapshot for useSyncExternalStore.
150
+ *
151
+ * @returns {string}
152
+ */
153
+ export function getToolbarToolStatesSnapshot() {
154
+ return String(_snapshotVersion)
155
+ }
156
+
157
+ // ---------------------------------------------------------------------------
158
+ // Internal
159
+ // ---------------------------------------------------------------------------
160
+
161
+ function _notify() {
162
+ _snapshotVersion++
163
+ for (const cb of _listeners) {
164
+ try { cb() } catch (err) {
165
+ console.error('[storyboard] Error in tool state subscriber:', err)
166
+ }
167
+ }
168
+ }
169
+
170
+ // ---------------------------------------------------------------------------
171
+ // Test helpers
172
+ // ---------------------------------------------------------------------------
173
+
174
+ /** Reset all state. Only for tests. */
175
+ export function _resetToolbarToolStates() {
176
+ _states = new Map()
177
+ _localOnlyFlags = new Map()
178
+ _listeners.clear()
179
+ _snapshotVersion = 0
180
+ }
@@ -0,0 +1,204 @@
1
+ import {
2
+ TOOL_STATES,
3
+ initToolbarToolStates,
4
+ setToolbarToolState,
5
+ getToolbarToolState,
6
+ isToolbarToolLocalOnly,
7
+ subscribeToToolbarToolStates,
8
+ getToolbarToolStatesSnapshot,
9
+ _resetToolbarToolStates,
10
+ } from './toolStateStore.js'
11
+
12
+ afterEach(() => {
13
+ _resetToolbarToolStates()
14
+ })
15
+
16
+ describe('toolStateStore', () => {
17
+ describe('TOOL_STATES', () => {
18
+ it('exports all 5 state constants', () => {
19
+ expect(TOOL_STATES.ACTIVE).toBe('active')
20
+ expect(TOOL_STATES.INACTIVE).toBe('inactive')
21
+ expect(TOOL_STATES.HIDDEN).toBe('hidden')
22
+ expect(TOOL_STATES.DIMMED).toBe('dimmed')
23
+ expect(TOOL_STATES.DISABLED).toBe('disabled')
24
+ expect(Object.keys(TOOL_STATES)).toHaveLength(5)
25
+ })
26
+ })
27
+
28
+ describe('getToolbarToolState', () => {
29
+ it('returns "active" for unknown tool IDs', () => {
30
+ expect(getToolbarToolState('nonexistent')).toBe('active')
31
+ })
32
+
33
+ it('returns "active" after init with no state in config', () => {
34
+ initToolbarToolStates({ myTool: {} })
35
+ expect(getToolbarToolState('myTool')).toBe('active')
36
+ })
37
+ })
38
+
39
+ describe('initToolbarToolStates', () => {
40
+ it('seeds states from config', () => {
41
+ initToolbarToolStates({
42
+ inspector: { state: 'hidden' },
43
+ comments: { state: 'dimmed' },
44
+ })
45
+ expect(getToolbarToolState('inspector')).toBe('hidden')
46
+ expect(getToolbarToolState('comments')).toBe('dimmed')
47
+ })
48
+
49
+ it('defaults to active when no state specified', () => {
50
+ initToolbarToolStates({ inspector: { render: 'panel' } })
51
+ expect(getToolbarToolState('inspector')).toBe('active')
52
+ })
53
+
54
+ it('reads state from config when specified', () => {
55
+ initToolbarToolStates({ inspector: { state: 'inactive' } })
56
+ expect(getToolbarToolState('inspector')).toBe('inactive')
57
+ })
58
+
59
+ it('localOnly + !isLocalDev → disabled (overrides config state)', () => {
60
+ initToolbarToolStates(
61
+ { devTool: { localOnly: true, state: 'active' } },
62
+ { isLocalDev: false },
63
+ )
64
+ expect(getToolbarToolState('devTool')).toBe('disabled')
65
+ })
66
+
67
+ it('localOnly + isLocalDev → uses config state (active by default)', () => {
68
+ initToolbarToolStates(
69
+ { devTool: { localOnly: true } },
70
+ { isLocalDev: true },
71
+ )
72
+ expect(getToolbarToolState('devTool')).toBe('active')
73
+ })
74
+
75
+ it('handles empty config', () => {
76
+ initToolbarToolStates({})
77
+ expect(getToolbarToolState('anything')).toBe('active')
78
+ })
79
+
80
+ it('replaces previous state on re-init', () => {
81
+ initToolbarToolStates({ inspector: { state: 'hidden' } })
82
+ expect(getToolbarToolState('inspector')).toBe('hidden')
83
+
84
+ initToolbarToolStates({ inspector: { state: 'dimmed' } })
85
+ expect(getToolbarToolState('inspector')).toBe('dimmed')
86
+ })
87
+ })
88
+
89
+ describe('setToolbarToolState', () => {
90
+ it('updates state for a known tool', () => {
91
+ initToolbarToolStates({ inspector: {} })
92
+ setToolbarToolState('inspector', 'hidden')
93
+ expect(getToolbarToolState('inspector')).toBe('hidden')
94
+ })
95
+
96
+ it('updates state for unknown tool (creates entry)', () => {
97
+ setToolbarToolState('newTool', 'dimmed')
98
+ expect(getToolbarToolState('newTool')).toBe('dimmed')
99
+ })
100
+
101
+ it('warns on invalid state value', () => {
102
+ const spy = vi.spyOn(console, 'warn').mockImplementation(() => {})
103
+ setToolbarToolState('inspector', 'bogus')
104
+ expect(spy).toHaveBeenCalledOnce()
105
+ expect(spy.mock.calls[0][0]).toContain('Invalid tool state')
106
+ spy.mockRestore()
107
+ })
108
+
109
+ it('notifies subscribers on change', () => {
110
+ const cb = vi.fn()
111
+ subscribeToToolbarToolStates(cb)
112
+ initToolbarToolStates({ inspector: {} })
113
+ const callsBefore = cb.mock.calls.length
114
+
115
+ setToolbarToolState('inspector', 'inactive')
116
+ expect(cb).toHaveBeenCalledTimes(callsBefore + 1)
117
+ })
118
+ })
119
+
120
+ describe('isToolbarToolLocalOnly', () => {
121
+ it('returns true for localOnly tools', () => {
122
+ initToolbarToolStates(
123
+ { devTool: { localOnly: true } },
124
+ { isLocalDev: true },
125
+ )
126
+ expect(isToolbarToolLocalOnly('devTool')).toBe(true)
127
+ })
128
+
129
+ it('returns false for non-localOnly tools', () => {
130
+ initToolbarToolStates({ inspector: {} })
131
+ expect(isToolbarToolLocalOnly('inspector')).toBe(false)
132
+ })
133
+
134
+ it('returns false for unknown tools', () => {
135
+ expect(isToolbarToolLocalOnly('nonexistent')).toBe(false)
136
+ })
137
+ })
138
+
139
+ describe('subscribeToToolbarToolStates', () => {
140
+ it('calls callback on state changes', () => {
141
+ const cb = vi.fn()
142
+ subscribeToToolbarToolStates(cb)
143
+ initToolbarToolStates({ inspector: {} })
144
+ expect(cb).toHaveBeenCalled()
145
+ })
146
+
147
+ it('returns working unsubscribe function', () => {
148
+ const cb = vi.fn()
149
+ const unsub = subscribeToToolbarToolStates(cb)
150
+ unsub()
151
+
152
+ initToolbarToolStates({ inspector: {} })
153
+ expect(cb).not.toHaveBeenCalled()
154
+ })
155
+
156
+ it('supports multiple subscribers', () => {
157
+ const cb1 = vi.fn()
158
+ const cb2 = vi.fn()
159
+ subscribeToToolbarToolStates(cb1)
160
+ subscribeToToolbarToolStates(cb2)
161
+
162
+ initToolbarToolStates({ inspector: {} })
163
+ expect(cb1).toHaveBeenCalled()
164
+ expect(cb2).toHaveBeenCalled()
165
+ })
166
+ })
167
+
168
+ describe('getToolbarToolStatesSnapshot', () => {
169
+ it('returns string', () => {
170
+ expect(typeof getToolbarToolStatesSnapshot()).toBe('string')
171
+ })
172
+
173
+ it('changes on mutation', () => {
174
+ const before = getToolbarToolStatesSnapshot()
175
+ initToolbarToolStates({ inspector: {} })
176
+ const after = getToolbarToolStatesSnapshot()
177
+ expect(after).not.toBe(before)
178
+ })
179
+
180
+ it('does not change without mutation', () => {
181
+ const a = getToolbarToolStatesSnapshot()
182
+ const b = getToolbarToolStatesSnapshot()
183
+ expect(a).toBe(b)
184
+ })
185
+ })
186
+
187
+ describe('_resetToolbarToolStates', () => {
188
+ it('clears all state', () => {
189
+ initToolbarToolStates({ inspector: { state: 'hidden', localOnly: true } }, { isLocalDev: true })
190
+ _resetToolbarToolStates()
191
+ expect(getToolbarToolState('inspector')).toBe('active')
192
+ expect(isToolbarToolLocalOnly('inspector')).toBe(false)
193
+ })
194
+
195
+ it('clears all listeners', () => {
196
+ const cb = vi.fn()
197
+ subscribeToToolbarToolStates(cb)
198
+ _resetToolbarToolStates()
199
+
200
+ initToolbarToolStates({ inspector: {} })
201
+ expect(cb).not.toHaveBeenCalled()
202
+ })
203
+ })
204
+ })