@cyann/dash-lab 0.0.0-stage → 0.1.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 (96) hide show
  1. package/AGENTS.md +138 -0
  2. package/CHANGELOG.md +48 -0
  3. package/CONTRIBUTING.md +94 -0
  4. package/LICENSE +21 -0
  5. package/README.md +154 -2
  6. package/THIRD_PARTY_NOTICES.md +31 -0
  7. package/dist/commands/CommandContext.d.ts +19 -0
  8. package/dist/commands/CommandContext.d.ts.map +1 -0
  9. package/dist/commands/CommandPalette.d.ts +10 -0
  10. package/dist/commands/CommandPalette.d.ts.map +1 -0
  11. package/dist/commands/CommandProvider.d.ts +12 -0
  12. package/dist/commands/CommandProvider.d.ts.map +1 -0
  13. package/dist/commands/CommandRegistry.d.ts +40 -0
  14. package/dist/commands/CommandRegistry.d.ts.map +1 -0
  15. package/dist/commands/searchCommands.d.ts +3 -0
  16. package/dist/commands/searchCommands.d.ts.map +1 -0
  17. package/dist/commands/shortcuts.d.ts +7 -0
  18. package/dist/commands/shortcuts.d.ts.map +1 -0
  19. package/dist/dash-lab.css +2 -0
  20. package/dist/i18n/I18nProvider.d.ts +20 -0
  21. package/dist/i18n/I18nProvider.d.ts.map +1 -0
  22. package/dist/i18n/i18nStore.d.ts +31 -0
  23. package/dist/i18n/i18nStore.d.ts.map +1 -0
  24. package/dist/i18n/messages.d.ts +3 -0
  25. package/dist/i18n/messages.d.ts.map +1 -0
  26. package/dist/index.d.ts +32 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +2561 -0
  29. package/dist/layout/DockWorkspace.d.ts +7 -0
  30. package/dist/layout/DockWorkspace.d.ts.map +1 -0
  31. package/dist/layout/LayoutControls.d.ts +5 -0
  32. package/dist/layout/LayoutControls.d.ts.map +1 -0
  33. package/dist/layout/Workbench.d.ts +40 -0
  34. package/dist/layout/Workbench.d.ts.map +1 -0
  35. package/dist/layout/layoutPersistence.d.ts +22 -0
  36. package/dist/layout/layoutPersistence.d.ts.map +1 -0
  37. package/dist/layout/layoutStore.d.ts +13 -0
  38. package/dist/layout/layoutStore.d.ts.map +1 -0
  39. package/dist/layout/panels.d.ts +21 -0
  40. package/dist/layout/panels.d.ts.map +1 -0
  41. package/dist/layout/useLayoutManagement.d.ts +21 -0
  42. package/dist/layout/useLayoutManagement.d.ts.map +1 -0
  43. package/dist/layout/useWorkbenchCommands.d.ts +14 -0
  44. package/dist/layout/useWorkbenchCommands.d.ts.map +1 -0
  45. package/dist/library.d.ts +3 -0
  46. package/dist/library.d.ts.map +1 -0
  47. package/dist/notifications/NotificationButton.d.ts +3 -0
  48. package/dist/notifications/NotificationButton.d.ts.map +1 -0
  49. package/dist/notifications/NotificationContext.d.ts +5 -0
  50. package/dist/notifications/NotificationContext.d.ts.map +1 -0
  51. package/dist/notifications/NotificationProvider.d.ts +11 -0
  52. package/dist/notifications/NotificationProvider.d.ts.map +1 -0
  53. package/dist/notifications/NotificationToasts.d.ts +2 -0
  54. package/dist/notifications/NotificationToasts.d.ts.map +1 -0
  55. package/dist/notifications/NotificationViewport.d.ts +2 -0
  56. package/dist/notifications/NotificationViewport.d.ts.map +1 -0
  57. package/dist/notifications/notificationStore.d.ts +56 -0
  58. package/dist/notifications/notificationStore.d.ts.map +1 -0
  59. package/dist/notifications/preferences.d.ts +10 -0
  60. package/dist/notifications/preferences.d.ts.map +1 -0
  61. package/dist/overlays/dialogMotion.d.ts +3 -0
  62. package/dist/overlays/dialogMotion.d.ts.map +1 -0
  63. package/dist/overlays/useDialogDismiss.d.ts +9 -0
  64. package/dist/overlays/useDialogDismiss.d.ts.map +1 -0
  65. package/dist/plugins/PluginContext.d.ts +5 -0
  66. package/dist/plugins/PluginContext.d.ts.map +1 -0
  67. package/dist/plugins/PluginRegistry.d.ts +98 -0
  68. package/dist/plugins/PluginRegistry.d.ts.map +1 -0
  69. package/dist/plugins/pluginState.d.ts +19 -0
  70. package/dist/plugins/pluginState.d.ts.map +1 -0
  71. package/dist/settings/ApplicationMenu.d.ts +2 -0
  72. package/dist/settings/ApplicationMenu.d.ts.map +1 -0
  73. package/dist/settings/SettingsDialog.d.ts +12 -0
  74. package/dist/settings/SettingsDialog.d.ts.map +1 -0
  75. package/dist/settings/configuration.d.ts +36 -0
  76. package/dist/settings/configuration.d.ts.map +1 -0
  77. package/dist/settings/useWorkbenchConfiguration.d.ts +15 -0
  78. package/dist/settings/useWorkbenchConfiguration.d.ts.map +1 -0
  79. package/dist/theme/ThemeProvider.d.ts +17 -0
  80. package/dist/theme/ThemeProvider.d.ts.map +1 -0
  81. package/dist/theme/ThemeToggle.d.ts +3 -0
  82. package/dist/theme/ThemeToggle.d.ts.map +1 -0
  83. package/dist/theme/palettes.d.ts +153 -0
  84. package/dist/theme/palettes.d.ts.map +1 -0
  85. package/dist/theme/themeStore.d.ts +31 -0
  86. package/dist/theme/themeStore.d.ts.map +1 -0
  87. package/docs/api.md +171 -0
  88. package/docs/application-guide.md +332 -0
  89. package/docs/architecture.md +59 -0
  90. package/docs/decisions/001-library-and-demo.md +9 -0
  91. package/docs/decisions/002-local-plugins-and-languages.md +17 -0
  92. package/docs/decisions/003-plugin-state-checkpoints.md +11 -0
  93. package/docs/dependencies.md +33 -0
  94. package/docs/validation.md +126 -0
  95. package/package.json +97 -4
  96. package/scripts/install-hooks.mjs +17 -0
@@ -0,0 +1,3 @@
1
+ /** An accessible theme switch bound to the nearest ThemeProvider. */
2
+ export declare function ThemeToggle(): import("react").JSX.Element;
3
+ //# sourceMappingURL=ThemeToggle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ThemeToggle.d.ts","sourceRoot":"","sources":["../../src/theme/ThemeToggle.tsx"],"names":[],"mappings":"AAGA,qEAAqE;AACrE,wBAAgB,WAAW,gCAoB1B"}
@@ -0,0 +1,153 @@
1
+ export declare const palettes: {
2
+ light: {
3
+ '--dl-canvas': string;
4
+ '--dl-surface': string;
5
+ '--dl-raised': string;
6
+ '--dl-text': string;
7
+ '--dl-muted': string;
8
+ '--dl-border': string;
9
+ '--dl-accent': string;
10
+ '--dl-accent-soft': string;
11
+ '--dl-on-accent': string;
12
+ '--dl-success': string;
13
+ '--dl-warning': string;
14
+ '--dl-error': string;
15
+ };
16
+ dark: {
17
+ '--dl-canvas': string;
18
+ '--dl-surface': string;
19
+ '--dl-raised': string;
20
+ '--dl-text': string;
21
+ '--dl-muted': string;
22
+ '--dl-border': string;
23
+ '--dl-accent': string;
24
+ '--dl-accent-soft': string;
25
+ '--dl-on-accent': string;
26
+ '--dl-success': string;
27
+ '--dl-warning': string;
28
+ '--dl-error': string;
29
+ };
30
+ };
31
+ export declare const familyPalettes: {
32
+ azure: {
33
+ light: {
34
+ '--dl-canvas': string;
35
+ '--dl-surface': string;
36
+ '--dl-raised': string;
37
+ '--dl-text': string;
38
+ '--dl-muted': string;
39
+ '--dl-border': string;
40
+ '--dl-accent': string;
41
+ '--dl-accent-soft': string;
42
+ '--dl-on-accent': string;
43
+ '--dl-success': string;
44
+ '--dl-warning': string;
45
+ '--dl-error': string;
46
+ };
47
+ dark: {
48
+ '--dl-canvas': string;
49
+ '--dl-surface': string;
50
+ '--dl-raised': string;
51
+ '--dl-text': string;
52
+ '--dl-muted': string;
53
+ '--dl-border': string;
54
+ '--dl-accent': string;
55
+ '--dl-accent-soft': string;
56
+ '--dl-on-accent': string;
57
+ '--dl-success': string;
58
+ '--dl-warning': string;
59
+ '--dl-error': string;
60
+ };
61
+ };
62
+ emerald: {
63
+ light: {
64
+ '--dl-canvas': string;
65
+ '--dl-surface': string;
66
+ '--dl-raised': string;
67
+ '--dl-text': string;
68
+ '--dl-muted': string;
69
+ '--dl-border': string;
70
+ '--dl-accent': string;
71
+ '--dl-accent-soft': string;
72
+ '--dl-on-accent': string;
73
+ '--dl-success': string;
74
+ '--dl-warning': string;
75
+ '--dl-error': string;
76
+ };
77
+ dark: {
78
+ '--dl-canvas': string;
79
+ '--dl-surface': string;
80
+ '--dl-raised': string;
81
+ '--dl-text': string;
82
+ '--dl-muted': string;
83
+ '--dl-border': string;
84
+ '--dl-accent': string;
85
+ '--dl-accent-soft': string;
86
+ '--dl-on-accent': string;
87
+ '--dl-success': string;
88
+ '--dl-warning': string;
89
+ '--dl-error': string;
90
+ };
91
+ };
92
+ umber: {
93
+ light: {
94
+ '--dl-canvas': string;
95
+ '--dl-surface': string;
96
+ '--dl-raised': string;
97
+ '--dl-text': string;
98
+ '--dl-muted': string;
99
+ '--dl-border': string;
100
+ '--dl-accent': string;
101
+ '--dl-accent-soft': string;
102
+ '--dl-on-accent': string;
103
+ '--dl-success': string;
104
+ '--dl-warning': string;
105
+ '--dl-error': string;
106
+ };
107
+ dark: {
108
+ '--dl-canvas': string;
109
+ '--dl-surface': string;
110
+ '--dl-raised': string;
111
+ '--dl-text': string;
112
+ '--dl-muted': string;
113
+ '--dl-border': string;
114
+ '--dl-accent': string;
115
+ '--dl-accent-soft': string;
116
+ '--dl-on-accent': string;
117
+ '--dl-success': string;
118
+ '--dl-warning': string;
119
+ '--dl-error': string;
120
+ };
121
+ };
122
+ contrast: {
123
+ light: {
124
+ '--dl-canvas': string;
125
+ '--dl-surface': string;
126
+ '--dl-raised': string;
127
+ '--dl-text': string;
128
+ '--dl-muted': string;
129
+ '--dl-border': string;
130
+ '--dl-accent': string;
131
+ '--dl-accent-soft': string;
132
+ '--dl-on-accent': string;
133
+ '--dl-success': string;
134
+ '--dl-warning': string;
135
+ '--dl-error': string;
136
+ };
137
+ dark: {
138
+ '--dl-canvas': string;
139
+ '--dl-surface': string;
140
+ '--dl-raised': string;
141
+ '--dl-text': string;
142
+ '--dl-muted': string;
143
+ '--dl-border': string;
144
+ '--dl-accent': string;
145
+ '--dl-accent-soft': string;
146
+ '--dl-on-accent': string;
147
+ '--dl-success': string;
148
+ '--dl-warning': string;
149
+ '--dl-error': string;
150
+ };
151
+ };
152
+ };
153
+ //# sourceMappingURL=palettes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"palettes.d.ts","sourceRoot":"","sources":["../../src/theme/palettes.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BgC,CAAC;AAEtD,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkFqB,CAAC"}
@@ -0,0 +1,31 @@
1
+ /** Supported workbench color schemes. */
2
+ export type ThemeMode = 'light' | 'dark';
3
+ /** Color families, each available in light and dark. */
4
+ export type ThemeFamily = 'azure' | 'contrast' | 'emerald' | 'umber';
5
+ export declare const themeFamilies: {
6
+ readonly azure: "Azure";
7
+ readonly contrast: "High Contrast";
8
+ readonly emerald: "Emerald";
9
+ readonly umber: "Umber";
10
+ };
11
+ /** Follow the operating system or reduce motion unconditionally. */
12
+ export type MotionPreference = 'system' | 'reduced';
13
+ /** Theme state and actions available through useTheme. */
14
+ export interface ThemeControls {
15
+ /** The active color scheme. */
16
+ mode: ThemeMode;
17
+ /** Active palette, independent of the light/dark scheme. */
18
+ family: ThemeFamily;
19
+ /** Select a palette without changing the light/dark scheme. */
20
+ setFamily: (family: ThemeFamily) => void;
21
+ /** Animation preference; the operating system's reduced-motion setting always takes priority. */
22
+ motionPreference: MotionPreference;
23
+ /** Select the animation preference. */
24
+ setMotionPreference: (preference: MotionPreference) => void;
25
+ /** Select a color scheme for this provider. */
26
+ setMode: (mode: ThemeMode) => void;
27
+ /** Switch between light and dark. */
28
+ toggleTheme: () => void;
29
+ }
30
+ export declare function createThemeStore(mode: ThemeMode, family?: ThemeFamily): import("zustand").StoreApi<ThemeControls>;
31
+ //# sourceMappingURL=themeStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"themeStore.d.ts","sourceRoot":"","sources":["../../src/theme/themeStore.ts"],"names":[],"mappings":"AAEA,yCAAyC;AACzC,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;AAEzC,wDAAwD;AACxD,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,OAAO,CAAC;AAErE,eAAO,MAAM,aAAa;;;;;CAA6F,CAAC;AAExH,oEAAoE;AACpE,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEpD,0DAA0D;AAC1D,MAAM,WAAW,aAAa;IAC5B,+BAA+B;IAC/B,IAAI,EAAE,SAAS,CAAC;IAChB,4DAA4D;IAC5D,MAAM,EAAE,WAAW,CAAC;IACpB,+DAA+D;IAC/D,SAAS,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;IACzC,iGAAiG;IACjG,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,uCAAuC;IACvC,mBAAmB,EAAE,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC5D,+CAA+C;IAC/C,OAAO,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IACnC,qCAAqC;IACrC,WAAW,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,GAAE,WAAqB,6CAU9E"}
package/docs/api.md ADDED
@@ -0,0 +1,171 @@
1
+ # Public API
2
+
3
+ Import JavaScript/types from `@cyann/dash-lab` and styles from `@cyann/dash-lab/style.css`. The package is ESM and requires React/React DOM 19 peers. Public declarations ship in `dist/`; source JSDoc and this document describe the same contracts. The package scope does not change the `dash-lab` JSON format identifier or existing browser-storage keys.
4
+
5
+ ## Workbench
6
+
7
+ ```tsx
8
+ import { Workbench } from '@cyann/dash-lab';
9
+ import type { WorkbenchPanel } from '@cyann/dash-lab';
10
+ import '@cyann/dash-lab/style.css';
11
+
12
+ const panels: readonly WorkbenchPanel[] = [{ id: 'main', title: 'Main', component: () => <p>Ready</p> }];
13
+ export function App() {
14
+ return (
15
+ <div style={{ height: '100vh' }}>
16
+ <Workbench title="My Lab" panels={panels} />
17
+ </div>
18
+ );
19
+ }
20
+ ```
21
+
22
+ `WorkbenchProps`:
23
+
24
+ | Property | Contract |
25
+ | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
26
+ | `title` | Required application name; also supplies default storage-key identity. |
27
+ | `subtitle`, `logo`, `headerActions` | Optional header text and React content. |
28
+ | `controlBar`, `monitoringBar`, `footer` | Optional React content in the corresponding chrome regions. |
29
+ | `panels` | Stable initial panel definitions. Plugin panels precede these. |
30
+ | `plugins` | Optional `PluginRegistry`, sealed on first mount. |
31
+ | `onWorkspaceReady(api)` | Called after initial panels and compatible saved layout restoration. Receives the native Dockview API. |
32
+ | `initialHeaderVisible`, `initialFooterVisible` | Initial visibility, both defaulting to true. Saved layout preferences take precedence. |
33
+ | `layoutStorageKey` | Automatic custom-layout storage key, or false for session-only checkpoints. |
34
+ | `preferencesStorageKey` | Automatically persisted preferences key, or false for in-memory preferences. |
35
+ | `children` | Fallback content when no panels exist. |
36
+
37
+ Keep panel collections, plugin registries, and storage keys stable until unmount. Default keys are `dash-lab:${encodeURIComponent(title)}:layout:v1` and `dash-lab:${encodeURIComponent(title)}:preferences:v1`. Applications sharing a title should choose distinct keys when their configuration must be independent.
38
+
39
+ `WorkbenchPanel` has `id`, `title`, `component`, optional `position: { referencePanel, direction }`, and optional positive `initialWidth`/`initialHeight`. Directions are left, right, above, below, and within. Reference panels must precede their dependants. IDs stay stable across languages and persisted layouts. Components render in independent scrolling containers and remain mounted when inactive or when chrome changes.
40
+
41
+ The toolbar offers Application, Columns, Rows, and Custom layout. Manual docking, resizing, and active-tab changes automatically update the custom snapshot after 200 ms of inactivity. Presets, theme/language changes, host resizing and Focus keep it available; the Custom layout button restores it. Header/footer choices persist immediately without replacing its arrangement. Storage failures retain the snapshot in memory and produce a warning. Legacy Save/Restore/Reset command IDs and shortcuts remain supported.
42
+
43
+ View hides/shows header and footer together. Settings and existing individual shortcuts still control each region. Focus suppresses all chrome; Escape restores previous visibility choices. Neither changes the mounted panel instances.
44
+
45
+ ## Providers and hooks
46
+
47
+ Workbench reuses the nearest providers or supplies defaults. For explicit composition, use:
48
+
49
+ ```tsx
50
+ <I18nProvider>
51
+ <ThemeProvider initialMode="dark">
52
+ <NotificationProvider historyLimit={100}>
53
+ <CommandProvider registry={registry}>
54
+ <Workbench title="My Lab" panels={panels} />
55
+ </CommandProvider>
56
+ </NotificationProvider>
57
+ </ThemeProvider>
58
+ </I18nProvider>
59
+ ```
60
+
61
+ Use one command registry per workbench. Provider props are initial configuration, not controlled replacements. A surrounding router is optional and belongs to the consuming application; the demo uses React Router. Hooks below are called from React components, not from plugin activation functions.
62
+
63
+ Automatic providers cover workbench descendants. If an application component calls `useI18n` to construct header/footer/monitoring props, place an explicit `I18nProvider` above that application component; a provider inside its returned Workbench cannot provide context to its parent.
64
+
65
+ ### Theme
66
+
67
+ `ThemeProviderProps` contains `children` and optional `initialMode: 'light' | 'dark'`. `useTheme(): ThemeControls` returns `mode`, `setMode`, `toggleTheme`, `motionPreference`, and `setMotionPreference`. `MotionPreference` is system or reduced. System reduced motion always wins. `ThemeToggle` is an exported ready-made control.
68
+
69
+ Theme changes update CSS tokens, Ant Design, and Dockview without resetting content. Standalone providers keep choices in memory; a workbench persists its preferences. The public tokens are canvas, surface, raised, text, muted, border, accent, accent-soft, on-accent, success, warning, and error, each prefixed with `--dl-`.
70
+
71
+ ### Notifications
72
+
73
+ `NotificationProviderProps` contains `children` and optional positive integer `historyLimit` (default 100). `useNotifications(): NotificationCenterState` exposes:
74
+
75
+ | Operation/state | Meaning |
76
+ | -------------------------------------- | ---------------------------------------------------------------------------------- |
77
+ | `notify(input): string` | Adds a notification and returns its center-local ID. |
78
+ | `items`, `toasts`, `open` | Newest-first history, up to three active toast IDs, and history-dialog visibility. |
79
+ | `dismiss(id)` | Hides a toast without reading or removing history. |
80
+ | `markRead(id)`, `markAllRead()` | Acknowledge notifications. |
81
+ | `remove(id)`, `clearHistory()` | Remove entries and related active toasts. |
82
+ | `setOpen(boolean)` | Opens/closes history. |
83
+ | `preferences`, `setPreferences(value)` | Popup visibility and default duration. |
84
+
85
+ `NotificationInput` requires a nonempty `title`, accepts a plain-text `description`, optional level info/success/warning/error, and `durationMs`. Zero means sticky; errors default to zero and other levels initially to 5000 ms. An explicit duration overrides the preference. `NotificationItem` adds readonly ID, creation timestamp, read flag, resolved level, and duration. Disabling popups clears active toasts while retaining history; re-enabling does not replay entries. `NotificationPreferences` is `{ toastsEnabled, toastDurationMs }`, with default duration restricted to 1–30 whole seconds. `NotificationButton` is exported for custom chrome.
86
+
87
+ Toast dismissal, expiry, history removal/clearing, popup disabling and overflow replacement slide/fade the popup out before removing its visual snapshot. Store updates remain immediate. Closing popups are inert and hidden from assistive technology; reduced motion removes them immediately. Dismissal/expiry do not alter history or unread flags.
88
+
89
+ Notification history is session state. Application text stays in the language used when delivered. React server rendering reads initial store snapshots; live notification/timer behavior is a browser concern.
90
+
91
+ ### Commands
92
+
93
+ `CommandProviderProps` contains `children` and optional stable `registry`. `useCommands(): CommandControls` exposes `register`, `execute`, `canExecute`, and reactive `commands`. Register in an effect and return the disposer:
94
+
95
+ ```tsx
96
+ useEffect(
97
+ () =>
98
+ register({
99
+ id: 'app.greet',
100
+ title: 'Say hello',
101
+ shortcut: 'Mod+Enter',
102
+ execute: () => {
103
+ notify({ title: 'Hello!' });
104
+ },
105
+ }),
106
+ [register, notify],
107
+ );
108
+ ```
109
+
110
+ `CommandDefinition` contains `id`, `title`, `execute`, optional `shortcut`, `isEnabled`, and `allowInEditable`. Handlers may return promises. `execute` returns false for missing, disabled, or already-running actions. The provider reports failures to the nearest notification center; without one, errors propagate. Predicates are live; callers of `canExecute` should handle predicate errors.
111
+
112
+ `CommandRegistry(platform?)` is independent of React/browser globals. Platform is mac or other (default other). It offers `register`, `execute`, `canExecute`, `getCommands`, `subscribe`, and `matchShortcut`. Registrations reject duplicate IDs and equivalent shortcuts before mutation. Disposers are idempotent and do not delete a later replacement. Running state belongs to the registration, not only its ID. Engine execution errors always reject.
113
+
114
+ `Mod` maps to Command on macOS and Control elsewhere. Shortcuts require exact modifiers. Supported keys include letters/digits, function keys, Enter, Escape, Tab, arrows, Home/End, PageUp/PageDown, Backspace/Delete/Insert, Space, and Plus. Application shortcuts ignore editable controls by default; opt in explicitly when needed. Workbench dispatch ignores repeats, IME composition, AltGraph, handled events, and modal dialogs. Dispatch is scoped to the focused workbench. `ShortcutEvent` and `ShortcutPlatform` are public types.
115
+
116
+ Built-in IDs reserve `workbench.*`. Mod+Alt combinations are T for theme, V for View, F for Focus, H/B for individual header/footer, S/R for save/restore, 0 for reset, 1/2/3 for presets, and N for notifications. Mod+Shift+P opens the palette. Escape leaves Focus. Settings, Preferences, and configuration import/export also have registered commands.
117
+
118
+ `CommandPaletteProps` is `{ open, onClose }`. The controlled `CommandPalette` needs command and notification providers. It searches translated titles and stable IDs, supports arrows/Enter/Escape, shows unavailable actions, and keeps six recent successful choices in memory. It closes before executing an action that may open another modal.
119
+
120
+ Workbench dialogs (Commands, Settings/Preferences, and notification history) slide and fade on opening and closing, including their backdrops. Browsers with native discrete display/overlay transitions restore focus immediately while CSS finishes the exit. Other browsers, including Firefox, keep the native dialog open during the exit and restore focus when it finishes. Reopening cancels pending closure; palette actions wait for closure before opening another modal. System or explicit reduced-motion preferences disable both directions and close immediately. Storybook's `Workbench/Commands/AnimatedDialogs` example exercises these controls.
121
+
122
+ ## Plugin SDK
123
+
124
+ `WorkbenchPlugin` is `{ id, activate(api): void | PluginDisposer }`. `PluginRegistry.registerPlugin` calls synchronous activation and rolls back its registrations on failure. Its returned disposer removes contributions and calls optional cleanup; use it only before mounting. `PluginRegistry.seal()` returns one immutable `PluginContributions` snapshot; mounting calls it automatically. Registrations and disposal are rejected after sealing. Remount with a new registry to change membership.
125
+
126
+ `PluginAPI` exposes six contribution methods, each returning an idempotent `PluginDisposer`:
127
+
128
+ | Method | Value |
129
+ | ------------------------ | ---------------------------------------------------------------- |
130
+ | `registerPanel` | `WorkbenchPanel`; plugin panels precede application panels. |
131
+ | `registerCommand` | `PluginCommand`; normal command fields, with `execute(context)`. |
132
+ | `registerMenu` | `PluginMenu: { id, title, commandId }`; shown under Plugins. |
133
+ | `registerStatusItem` | `PluginItem: { id, component }`; shown in the footer. |
134
+ | `registerMonitoringItem` | `PluginItem`; shown in monitoring. |
135
+ | `registerState` | `PluginStateHandler`; one optional handler per plugin ID. |
136
+
137
+ `PluginCommandContext` supplies `notify` and `executeCommand`. Commands use the workbench's existing providers, error handling, and availability. IDs must be unique within each contribution kind. `workbench.*` command IDs are reserved. Commands conflicting with the live command registry report a notification and roll back the attempted plugin-command batch; other panel contributions remain usable. Unknown menu commands display as unavailable.
138
+
139
+ `PluginContributions` exposes readonly `pluginIds`, `panels`, `commands`, `menus`, `statusItems`, `monitoringItems`, and `stateHandlers`. The latter is an immutable record keyed by plugin ID. Definitions are shallow copied/frozen; application components and handler functions remain application-owned. Registries do not sandbox or remotely load code.
140
+
141
+ `registerLocalPlugins(modules)` accepts Vite's eager glob result `{ [path]: { default: WorkbenchPlugin } }`, registers modules in sorted path order, and rolls back prior plugins if startup fails. Use the standard application entry `src/plugins/<name>/index.ts`. Plugins are compiled into the application. Development membership changes restart the mounted workbench; production changes require rebuilding.
142
+
143
+ ### Plugin state
144
+
145
+ Register during activation with `api.registerState({ exportState, importState })`. The registry supplies the owning plugin ID automatically. Registration is optional and uses the same startup rollback, disposal, and sealing rules as other contributions. Callbacks remain live after sealing.
146
+
147
+ `PluginStateHandler.exportState(): unknown` must return JSON data: null, strings, booleans, finite numbers, arrays, and plain objects. Undefined, functions, class instances, promises, and cycles are rejected rather than silently lost. `PluginStateValue` describes accepted JSON values; `PluginStateSnapshot` maps plugin IDs to these values. Snapshots are independent copies of live data.
148
+
149
+ `PluginStateHandler.importState(value: unknown): void` must synchronously validate its plugin-specific shape/version and update its observable application store. Components subscribe to that store normally; restoring does not remount panels. Both callbacks must be synchronous. Include a version in returned data when your format evolves. An importer must also accept its own exported snapshot so rollback can restore it. External side effects are outside this state contract.
150
+
151
+ Automatic custom-layout checkpoints and the retained Save layout command capture all registered handlers in the same local checkpoint as the layout. Plugin data changes alone do not schedule an automatic layout checkpoint; JSON export always reads current plugin data. Restore and startup reload apply saved plugin data before restoring Dockview. Reset and presets only change layout/visibility. Legacy checkpoints without plugin data leave live plugin state untouched. A panel-less workbench can transfer plugin state through JSON import/export; local layout checkpoints require a docking workspace.
152
+
153
+ Only handlers whose IDs occur in the snapshot are imported. Missing entries leave current state intact; entries for unavailable/nonparticipating plugins are ignored and are not retained on subsequent export. Before importing, the framework captures the participants' previous states. If a handler or layout restoration fails, attempted handlers are restored in reverse order, including the failing handler. Rollback failures are reported explicitly; arbitrary plugin callbacks cannot be guaranteed recoverable.
154
+
155
+ ## Languages
156
+
157
+ `I18nProviderProps` contains `children`, optional `initialLocale` (English by default), optional `languages: LanguageDefinition[]`, and optional `antdLocales`. A language definition is `{ id, label, messages }`, where `id` is a BCP 47 identifier and `messages: MessageCatalog` maps keys to strings. English/French are built in; additions merge into their catalogs or introduce languages.
158
+
159
+ `useI18n(): I18nControls` returns `locale`, readonly `locales`, `setLocale`, `assertLocale`, and `t(message, parameters?)`. `MessageParameters` maps placeholder names to strings/numbers. Missing translations fall back to English, then the literal key; missing parameters stay visible. Text is escaped by React, never interpreted as markup. Outside a provider, reading English is supported and language mutation throws. Ant Design catalogs for additional languages can be supplied through `antdLocales`.
160
+
161
+ ## Configuration
162
+
163
+ `WorkbenchPreferences` contains `theme`, `motion`, `notifications`, and optional `locale` for backward compatibility. `WorkbenchConfiguration` is `{ format: 'dash-lab', version: 1, workspace, preferences, plugins? }`. `workspace` is the native Dockview checkpoint plus header/footer visibility, or null for a workbench without panels. `plugins` contains opted-in JSON data keyed by plugin ID, stored once at the configuration's top level. `LayoutPreset` is default, columns, or rows.
164
+
165
+ `decodeConfiguration(text, panels)` is a pure validator with a 1 MB UTF-8 byte limit, including plugin data. It checks format/version, preferences, workspace shape, panel compatibility, and plugin JSON structure before returning data. Registered-language availability and plugin-specific validation are checked by Workbench before applying a workspace. Legacy files without a locale select English; files without `plugins` leave live plugin data intact. Unregistered React panel state, notification history, and recent commands are excluded.
166
+
167
+ Export captures current choices and opted-in plugin state without changing an existing checkpoint. Import applies a compatible configuration and updates the custom layout/plugin checkpoint. Plugin import failure leaves layout and preferences unchanged and attempts plugin rollback. Writes to layout/preferences keys are independent; if either fails, session state remains usable and feedback reports incomplete persistence. Reset preserves the existing saved checkpoint. Popout browser windows are not restored.
168
+
169
+ Theme families are Azure (the original blue), High Contrast (presentation), Emerald (green), and Umber (brown). Each has independent light/dark variants. Select a family in the hamburger menu's Theme submenu or Preferences; the existing light/dark switch keeps that family. High Contrast uses stronger text/borders, larger controls and typography. Application-owned panel content should consume the shared tokens for matching readability.
170
+
171
+ `ThemeProvider` accepts optional `initialFamily` (`azure` by default). `useTheme()` exposes `family` and `setFamily(ThemeFamily)` alongside the existing mode controls. Workbench automatically persists the selected family in `preferences.themeFamily` and includes it in JSON transfers. Version-1 files without this field use Azure; unknown families are rejected before import.