@pstdio/workbench 0.5.0 → 0.7.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 (33) hide show
  1. package/README.md +66 -72
  2. package/dist/composition-resolver-types-CWykPyEK.js +241 -0
  3. package/dist/composition-resolver-types-CWykPyEK.js.map +1 -0
  4. package/dist/extensions.d.ts +380 -29
  5. package/dist/extensions.js +1366 -883
  6. package/dist/extensions.js.map +1 -1
  7. package/dist/file-renderer-view-B6_iR4EN.js +496 -0
  8. package/dist/file-renderer-view-B6_iR4EN.js.map +1 -0
  9. package/dist/file-section-navigation-CfYqVObc.js +30 -0
  10. package/dist/file-section-navigation-CfYqVObc.js.map +1 -0
  11. package/dist/index.d.ts +305 -17
  12. package/dist/index.js +2343 -2083
  13. package/dist/index.js.map +1 -1
  14. package/dist/layout-types-DMoZz38-.js.map +1 -1
  15. package/dist/react.d.ts +190 -16
  16. package/dist/react.js +2473 -2374
  17. package/dist/react.js.map +1 -1
  18. package/dist/storage.js +132 -155
  19. package/dist/storage.js.map +1 -1
  20. package/dist/testing.d.ts +177 -14
  21. package/dist/webview-runtime.js +1 -1
  22. package/dist/webview-runtime.js.map +1 -1
  23. package/dist/workbench-built-ins-BncLJfQA.js +1175 -0
  24. package/dist/workbench-built-ins-BncLJfQA.js.map +1 -0
  25. package/dist/workbench-menu-paths-Bb0ICxeG.js +20 -0
  26. package/dist/workbench-menu-paths-Bb0ICxeG.js.map +1 -0
  27. package/package.json +4 -4
  28. package/dist/context-key-service-B2K9LAec.js +0 -1034
  29. package/dist/context-key-service-B2K9LAec.js.map +0 -1
  30. package/dist/file-renderer-view-DrrhYnb_.js +0 -171
  31. package/dist/file-renderer-view-DrrhYnb_.js.map +0 -1
  32. package/dist/workbench-menu-paths-B_QJzcNU.js +0 -12
  33. package/dist/workbench-menu-paths-B_QJzcNU.js.map +0 -1
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # pstdio-workbench
2
2
 
3
- `pstdio-workbench` is the workbench composition layer for Prompt Studio. It provides a typed core for registering contributions and a React workbench for rendering those contributions.
3
+ `pstdio-workbench` is the workbench composition layer for Prompt Studio. It provides a typed core for registering contributions and a React workbench for rendering those contributions. See the [Workbench reference](../../.pstdio/docs/references/workbench/index.md) for the full public API.
4
4
 
5
- ## Key Concepts
5
+ ## Key concepts
6
6
 
7
7
  - **Core**: the headless workbench model created by `createWorkbenchCore()`. It owns registries, controllers, and shared state.
8
8
  - **Workbench**: the React shell rendered by `Workbench`. It reads the core and turns registered contributions into visible UI.
@@ -17,29 +17,40 @@
17
17
 
18
18
  Contributions are the workbench extension points. A module contributes capabilities into the core; the React workbench renders the current state from those contributions. Contributions are grouped by role: layout, views, resources, navigation, and supporting plumbing. Ownership metadata is documented in [Contribution Ownership](https://github.com/pufflyai/prompt-studio/blob/main/.pstdio/docs/references/workbench/contribution-ownership.md).
19
19
 
20
- ## Public Entry Points
20
+ ## Public entry points
21
21
 
22
22
  - `@pstdio/workbench` exports the headless workbench core, registries, controllers, and contribution types.
23
23
  - `@pstdio/workbench/react` exports the React shell, view hosts, shared hooks, and `WorkbenchModuleHost` for syncing a dynamic module list into a core.
24
24
  - `@pstdio/workbench/storage` exports local-storage-backed layout and panel persistence adapters for hosts that want browser persistence with a namespace and scope.
25
+ - `@pstdio/workbench/extensions` maps checked extension metadata into workbench contributions.
26
+ - `@pstdio/workbench/testing` exports test fixtures and helpers.
27
+ - `@pstdio/workbench/webview-runtime` exports the runtime used inside extension webviews.
25
28
 
26
- ## 🏁 Layout Contributions
29
+ ## Layout contributions
27
30
 
28
- Layout contributions are the glue between a region and a view. They declare where a renderer appears in the shell, what config it receives, and how its placements behave without owning any UI themselves.
31
+ Layout contributions connect regions to views. They declare where a renderer appears, what configuration it receives, and how its placements behave. They do not render UI.
29
32
 
30
33
  ### Panels
31
34
 
32
- Pin a renderer to a region as a panel, editor, dashboard, inspector, status item, or overlay. Panels declare a `region`, `title`, `rendererId`, optional `resourceKinds`, optional sizing and collapsibility, and renderer-owned `config`. They do not contain render code the shell looks the `rendererId` up in the renderer registry.
35
+ Pin a renderer to a region as a panel, editor, dashboard, inspector, status item, or overlay. Panels declare a `region`, `title`, `rendererId`, optional `resourceKinds`, optional sizing and collapsibility, and renderer-owned `config`. They do not contain render code. The shell looks up the `rendererId` in the renderer registry.
33
36
 
34
37
  - Register with `layout.registerPanel()`
35
38
  - Set `singleton: true` for one placement total, such as a tree, sidenav, or status view.
36
39
  - Non-singleton Panels default to `reuse: "resource"`: one placement per resource URI, with no-resource opens reusing the Panel placement.
37
40
  - Set `reuse: "none"` for scratch, untitled, or transient views where every open should create a new placement.
38
41
 
42
+ `closable` and `role` belong to an open placement, not to panel registration. Extension composition derives both from `show` and the active mode recipe. Hosts that open a panel directly can set them in `layout.openPanel(...)`.
43
+
44
+ ### Composition
45
+
46
+ `workbench.composition.panelsFor(region)` returns the open placements, closed optional panels that can be added, and contribution ids that can be closed for `main`, `secondary`, or `side`. Use it to build panel controls that follow the active mode and resource. `sidenav` participates in extension placement but is not a tab-hosting panel region, so it is not accepted by this query.
47
+
48
+ See the `pstdio-workbench/API` Storybook section for live composition queries and an extension panel that moves between `main` and `sidenav`.
49
+
39
50
  #### Examples
40
51
 
41
- - [`hello-world`](src/examples/hello-world/module.tsx) minimal Panel pinned to `main`.
42
- - [`foundation`](src/examples/foundation/module.tsx) Panels in five different regions all backed by one shared renderer.
52
+ - [`hello-world`](src/examples/hello-world/module.tsx) has a minimal Panel pinned to `main`.
53
+ - [`foundation`](src/examples/foundation/module.tsx) places one shared renderer in five regions.
43
54
 
44
55
  ### Placeholders
45
56
 
@@ -49,7 +60,7 @@ Empty state for regions after every Panel in that region closes. Placeholders re
49
60
 
50
61
  #### Examples
51
62
 
52
- - [`hello-world`](src/examples/hello-world/module.tsx) placeholder shown in `main` when the welcome Panel is closed.
63
+ - [`hello-world`](src/examples/hello-world/module.tsx) shows a placeholder in `main` when the welcome Panel is closed.
53
64
 
54
65
  ### Menu items
55
66
 
@@ -59,14 +70,12 @@ Surface commands in the command palette, region headers, tree context menus, or
59
70
 
60
71
  #### Examples
61
72
 
62
- - [`hello-world`](src/examples/hello-world/module.tsx) main-header trailing menu item.
63
- - [`foundation`](src/examples/foundation/module.tsx) menu items wired into header paths.
64
-
65
- ---
73
+ - [`hello-world`](src/examples/hello-world/module.tsx) adds a trailing menu item to the main header.
74
+ - [`foundation`](src/examples/foundation/module.tsx) adds menu items to header paths.
66
75
 
67
- ## 👁️ View Contributions
76
+ ## View contributions
68
77
 
69
- View contributions are the UI itself — code that turns a placement, a tree node, or a collection into something a user sees and interacts with.
78
+ View contributions render placements, tree nodes, and collections as interactive UI.
70
79
 
71
80
  ### Renderers
72
81
 
@@ -77,23 +86,23 @@ The React or bridge implementation for a Panel or placeholder. Renderers turn a
77
86
 
78
87
  #### Examples
79
88
 
80
- - [`hello-world`](src/examples/hello-world/module.tsx) one renderer per Panel, plus a placeholder renderer.
81
- - [`foundation`](src/examples/foundation/module.tsx) one renderer reused across five Panels in different regions via `config`.
82
- - [`renderer-types`](src/examples/renderer-types/module.tsx) React and bridge renderers registered side by side.
83
- - [`keep-alive`](src/examples/keep-alive/module.tsx) keep-alive renderer shared by attached and floating Side Panel presentations.
89
+ - [`hello-world`](src/examples/hello-world/module.tsx) uses one renderer per Panel and one placeholder renderer.
90
+ - [`foundation`](src/examples/foundation/module.tsx) reuses one renderer across five Panels through `config`.
91
+ - [`renderer-types`](src/examples/renderer-types/module.tsx) registers React and bridge renderers together.
92
+ - [`keep-alive`](src/examples/keep-alive/module.tsx) shares one keep-alive renderer between attached and floating Side Panel presentations.
84
93
 
85
- ### Tree Renderers
94
+ ### Tree renderers
86
95
 
87
- A tree-shaped renderer for side-panel navigation, outlines, resource lists, and contextual hierarchies. Tree renderers expose body sections (`getBody`), optional footer nodes (`getFooter`), and lazy children (`getChildren`); nodes can carry resources, descriptions, inline actions, context menus, and `contextValue`. Placement is left to Panels — a tree renderer auto-registers a Panel renderer with the same id, so `layout.registerPanel({ rendererId: <tree id> })` plus `layout.openPanel(<tree id>)` puts the tree in any tree-hosting region.
96
+ A tree renderer handles side-panel navigation, outlines, resource lists, and contextual hierarchies. It exposes body sections through `getBody`, optional footer nodes through `getFooter`, and lazy children through `getChildren`. Nodes can carry resources, descriptions, inline actions, context menus, and `contextValue`. A tree renderer registers a Panel renderer with the same id. Use `layout.registerPanel({ rendererId: <tree id> })` and `layout.openPanel(<tree id>)` to place it in a tree-hosting region.
88
97
 
89
98
  - Register with `renderers.registerTreeRenderer()`
90
99
 
91
100
  #### Examples
92
101
 
93
- - [`dashboard`](src/examples/dashboard/modules/shell/project-nav.ts) primary tree with resource-backed nodes, footer entries, and context menus.
94
- - [`dynamic-modules`](src/examples/dynamic-modules/modules/explorer-module.tsx) tree contributed by a module that can be added and removed at runtime.
102
+ - [`dashboard`](src/examples/dashboard/modules/shell/project-nav.ts) builds the primary tree with resource-backed nodes, footer entries, and context menus.
103
+ - [`dynamic-modules`](src/examples/dynamic-modules/modules/explorer-module.tsx) adds and removes a tree module at runtime.
95
104
 
96
- ### Kanban Renderers
105
+ ### Kanban renderers
97
106
 
98
107
  A Notion/Linear-style data workspace registered as a renderer. Kanban renderers contribute the schema (tag definitions, grouping/ordering/display options, filter categories), the rows via `executeQuery(state)` (which receives current settings + filters so backends can push filter/sort/pagination down), and row-mutation callbacks. Like tree renderers, a kanban renderer auto-registers a Panel renderer with the same id, so the workspace is placed via `layout.registerPanel({ rendererId: <kanban renderer id> })` and opened with `layout.openPanel(...)`.
99
108
 
@@ -101,10 +110,10 @@ A Notion/Linear-style data workspace registered as a renderer. Kanban renderers
101
110
 
102
111
  #### Examples
103
112
 
104
- - [`kanban-renderer`](src/examples/kanban-renderer/module.tsx) focused showcase: schema, mock rows, and renderer-owned controls.
105
- - [`dashboard`](src/examples/dashboard/modules/tickets/collections/ticket-data.ts) ticket workspace integrated into the dashboard shell.
113
+ - [`kanban-renderer`](src/examples/kanban-renderer/module.tsx) shows a schema, mock rows, and renderer-owned controls.
114
+ - [`dashboard`](src/examples/dashboard/modules/tickets/collections/ticket-data.ts) integrates the ticket workspace into the dashboard shell.
106
115
 
107
- ### Data Table Renderers
116
+ ### Data table renderers
108
117
 
109
118
  A dense, query-driven table backed by `@pstdio/ui/data-table`. Data table renderers keep row ids and resources outside visible values, support declarative column labels, descriptions, icons, statistics and cell renderers, and use the table's local filtering, sorting, column controls, and pagination. They auto-register a Panel renderer with the same id and can be placed in any workbench region.
110
119
 
@@ -114,7 +123,7 @@ A dense, query-driven table backed by `@pstdio/ui/data-table`. Data table render
114
123
 
115
124
  #### Examples
116
125
 
117
- - [`data-table-renderer`](src/examples/data-table-renderer/module.tsx) service-health table with statistics, color scales, friendly JSON, navigation, and row actions.
126
+ - [`data-table-renderer`](src/examples/data-table-renderer/module.tsx) builds a service-health table with statistics, color scales, formatted JSON, navigation, and row actions.
118
127
 
119
128
  ### Breadcrumb (TODO: make it a renderer as well)
120
129
 
@@ -126,10 +135,8 @@ A dense, query-driven table backed by `@pstdio/ui/data-table`. Data table render
126
135
 
127
136
  #### Examples
128
137
 
129
- - [`dashboard`](src/examples/dashboard/modules/shell/components/dashboard-main-header.tsx) custom Main Panel header that places the view alongside workspace controls.
130
- - [`onboarding`](src/examples/onboarding/breadcrumb-module.tsx) three-level trail synchronized with page navigation.
131
-
132
- ---
138
+ - [`dashboard`](src/examples/dashboard/shared/resource-sync.ts) updates the breadcrumb trail when the open resource changes.
139
+ - [`onboarding`](src/examples/onboarding/breadcrumb-module.tsx) keeps a three-level trail in sync with page navigation.
133
140
 
134
141
  A typical surface combines both layers: a **renderer** supplies the UI, a **Panel** places it in a region with optional `config`, and `layout.openPanel()` creates the placement the user actually sees. Pick the narrowest contribution that matches what you are adding:
135
142
 
@@ -138,7 +145,7 @@ A typical surface combines both layers: a **renderer** supplies the UI, a **Pane
138
145
  - Add a **tree renderer** for navigable hierarchy and resource discovery, and place it with a Panel.
139
146
  - Add a **placeholder** for region-level empty state, not for normal content.
140
147
 
141
- ## 🗂️ Resource Contributions
148
+ ## Resource contributions
142
149
 
143
150
  Resource contributions define typed objects the workbench can open, route, or resolve from product data. They keep trees, navigation, and history speaking the same resource language.
144
151
 
@@ -151,8 +158,8 @@ Declare typed things the workbench can open. Resource refs carry `kind`, `uri`,
151
158
 
152
159
  #### Examples
153
160
 
154
- - [`renderer-types`](src/examples/renderer-types/module.tsx) single-kind module.
155
- - [`navigation`](src/examples/navigation/module.tsx) multiple kinds participating in routing.
161
+ - [`renderer-types`](src/examples/renderer-types/module.tsx) defines a module with one resource kind.
162
+ - [`navigation`](src/examples/navigation/module.tsx) uses several resource kinds in routing.
156
163
 
157
164
  ### Resource presenters
158
165
 
@@ -162,8 +169,8 @@ Map a Resource to the Panel instance that presents it. Presenters declare `canOp
162
169
 
163
170
  #### Examples
164
171
 
165
- - [`renderer-types`](src/examples/renderer-types/module.tsx) presenter routing one resource kind to one of two Panels.
166
- - [`navigation`](src/examples/navigation/module.tsx) presenters used by parsed navigation targets.
172
+ - [`renderer-types`](src/examples/renderer-types/module.tsx) routes one resource kind to one of two Panels.
173
+ - [`navigation`](src/examples/navigation/module.tsx) uses presenters for parsed navigation targets.
167
174
 
168
175
  ### Resource providers
169
176
 
@@ -173,9 +180,9 @@ Look up resources by kind, uri, or search input. Providers let features resolve
173
180
 
174
181
  #### Examples
175
182
 
176
- - [`dashboard`](src/examples/dashboard/modules/dashboard.tsx) provider behind the ticket KanbanView.
183
+ - [`dashboard`](src/examples/dashboard/modules/tickets/module.ts) provides ticket resources to the ticket board.
177
184
 
178
- ## 🧭 Navigation Contributions
185
+ ## Navigation contributions
179
186
 
180
187
  Navigation contributions turn incoming locations and resolved workbench targets into concrete workbench actions. They keep URL parsing, command dispatch, view opening, and resource routing centralized.
181
188
 
@@ -187,7 +194,7 @@ Turn ingress locations into workbench targets. Parsers convert URLs or location
187
194
 
188
195
  #### Examples
189
196
 
190
- - [`navigation`](src/examples/navigation/module.tsx) parses URL-like locations into typed targets.
197
+ - [`navigation`](src/examples/navigation/module.tsx) parses URL-like locations into typed targets.
191
198
 
192
199
  ### Navigation navigators
193
200
 
@@ -195,7 +202,7 @@ Custom dispatch behavior for navigation targets. Navigators let modules extend h
195
202
 
196
203
  - Register with `navigation.registerNavigator()`
197
204
 
198
- ## Other Contributions
205
+ ## Other contributions
199
206
 
200
207
  Supporting contributions make view, layout, resource, and navigation contributions useful. They define actions, persistence, scoping, and preferences without directly owning a workbench region.
201
208
 
@@ -207,8 +214,8 @@ Any executable workbench action. Commands are the primitive behind menus, keybin
207
214
 
208
215
  #### Examples
209
216
 
210
- - [`hello-world`](src/examples/hello-world/module.tsx) command that opens a Panel.
211
- - [`renderer-types`](src/examples/renderer-types/module.tsx) commands opening different placements.
217
+ - [`hello-world`](src/examples/hello-world/module.tsx) adds a command that opens a Panel.
218
+ - [`renderer-types`](src/examples/renderer-types/module.tsx) opens different placements with commands.
212
219
 
213
220
  ### Keybindings
214
221
 
@@ -218,8 +225,8 @@ Keyboard access to commands. Keybindings reference command ids and can include a
218
225
 
219
226
  #### Examples
220
227
 
221
- - [`foundation`](src/examples/foundation/module.tsx) keybinding gated by a context key.
222
- - [`random`](src/examples/random/modules/random-workbench.tsx) keybindings paired with mode-scoped commands.
228
+ - [`foundation`](src/examples/foundation/module.tsx) gates a keybinding with a context key.
229
+ - [`random`](src/examples/random/modules/random-workbench.tsx) pairs keybindings with mode-scoped commands.
223
230
 
224
231
  ### Context keys
225
232
 
@@ -229,8 +236,8 @@ Conditional command, menu, and keybinding behavior. Module contexts create scope
229
236
 
230
237
  #### Examples
231
238
 
232
- - [`foundation`](src/examples/foundation/module.tsx) sets `foundation.host` for menu and keybinding gating.
233
- - [`keep-alive`](src/examples/keep-alive/module.tsx) context key driving kept-alive subtree visibility.
239
+ - [`foundation`](src/examples/foundation/module.tsx) sets `foundation.host` to gate menus and keybindings.
240
+ - [`keep-alive`](src/examples/keep-alive/module.tsx) uses a context key to control kept-alive subtree visibility.
234
241
 
235
242
  ### Modes
236
243
 
@@ -240,8 +247,8 @@ Temporary bundles of contributions for workspace modes such as project, settings
240
247
 
241
248
  #### Examples
242
249
 
243
- - [`workbench-modes`](src/examples/workbench-modes/modules/project-mode.tsx) switching between mode-scoped bundles.
244
- - [`dashboard`](src/examples/dashboard/modules/project-mode.tsx) project and settings modes side by side.
250
+ - [`workbench-modes`](src/examples/workbench-modes/modules/project-mode.tsx) switches between mode-scoped bundles.
251
+ - [`dashboard`](src/examples/dashboard/modules/shell/module.tsx) defines the project mode and its navigation surfaces.
245
252
 
246
253
  ### Preference schemas
247
254
 
@@ -251,7 +258,7 @@ Typed settings contributed by a module or runtime extension. Schemas define the
251
258
 
252
259
  #### Examples
253
260
 
254
- - [`preferences`](src/examples/preferences/module.tsx) schema registration with user and workspace-scoped preference values.
261
+ - [`preferences`](src/examples/preferences/module.tsx) registers user and workspace-scoped preference values.
255
262
 
256
263
  ### Notifications
257
264
 
@@ -261,8 +268,8 @@ Toast-style messages from modules or extensions. Notifications can include comma
261
268
 
262
269
  #### Examples
263
270
 
264
- - [`dynamic-modules`](src/examples/dynamic-modules/modules/diagnostics-module.tsx) diagnostics module emitting toasts.
265
- - [`navigation`](src/examples/navigation/module.tsx) notification surfaced from a navigation flow.
271
+ - [`dynamic-modules`](src/examples/dynamic-modules/modules/diagnostics-module.tsx) emits toasts from a diagnostics module.
272
+ - [`navigation`](src/examples/navigation/module.tsx) sends a notification from a navigation flow.
266
273
 
267
274
  ### Themes
268
275
 
@@ -270,39 +277,26 @@ The React workbench owns the shared `@pstdio/ui` theme preference system through
270
277
 
271
278
  #### Examples
272
279
 
273
- - [`extension-themes`](src/examples/extension-themes/module.tsx) extensions registered as workbench modules, enabled and disabled at runtime, contributing VS Code-compatible color themes that restyle the workbench chrome.
280
+ - [`extension-themes`](src/examples/extension-themes/module.tsx) registers extensions as workbench modules that add and remove VS Code-compatible color themes at runtime.
274
281
 
275
282
  ### Terminal
276
283
 
277
- Terminals are a host-owned workbench surface, not extension-composed chrome. `workbench.terminal` (a core controller) owns the session registry; hosts inject how sessions actually open with `workbench.terminal.setSessionOpener(...)` a real PTY transport in production, `createScriptedTerminalBridge` from `@pstdio/ui/terminal` in stories and the extension testbench. `createWorkbenchTerminalModule()` registers the terminal panel in the `secondary` region plus the `workbench.terminal.open` command; the panel renders the shared `Terminal` component from `@pstdio/ui/terminal`. Closing the panel kills its session; disposing the controller kills every live session.
284
+ Terminals are owned by the host. Extensions do not compose their chrome. The `workbench.terminal` controller owns the session registry. Hosts supply a session opener through `workbench.terminal.setSessionOpener(...)`. Production uses a real PTY transport. Stories and the extension testbench use `createScriptedTerminalBridge` from `@pstdio/ui/terminal`. `createWorkbenchTerminalModule()` registers the terminal Panel in the `secondary` region and the `workbench.terminal.open` command. The Panel renders the shared `Terminal` component from `@pstdio/ui/terminal`. Closing the Panel kills its session. Disposing the controller kills every live session.
278
285
 
279
- Extension webviews never receive PTY handles. A webview that declares the `terminal.session` capability talks to the same session registry through serializable operations (`open`/`write`/`resize`/`kill`/`subscribe`), with output and exit events pushed over the bridge's host-event channel `createTerminalSessionBridge(host)` from `@pstdio/sdk/extensions` wraps that protocol into a bridge the `Terminal` component accepts.
286
+ Extension webviews never receive PTY handles. A webview with the `terminal.session` capability uses serializable `open`, `write`, `resize`, `kill`, and `subscribe` operations. The host sends output and exit events over the bridge event channel. `createTerminalSessionBridge(host)` from `@pstdio/sdk/extensions` turns that protocol into a bridge accepted by the `Terminal` component.
280
287
 
281
288
  #### Examples
282
289
 
283
- - [`workbench-modes`](src/examples/workbench-modes/module.tsx) workspace mode opening the host-owned terminal panel against a scripted backend.
284
- - [`workbench.stories`](src/examples/workbench.stories.tsx) `HostTerminal` story with the panel driven by `createScriptedTerminalBridge`.
285
-
286
- ---
290
+ - [`workbench-modes`](src/examples/workbench-modes/module.tsx) opens the host-owned terminal Panel with a scripted backend.
291
+ - [`workbench.stories`](src/examples/workbench.stories.tsx) drives the `HostTerminal` story with `createScriptedTerminalBridge`.
287
292
 
288
293
  Register related contributions inside one **workbench module** so they share ownership and disposal. For example, a ticket collection module usually contributes a resource kind, resource presenter, Panel, renderer, tree renderer, commands, and menu items together.
289
294
 
290
- ## Core
291
-
292
- The core is UI-independent. It is the source of truth that modules write to and the React workbench reads from.
293
- A host normally creates one core, registers modules into it, and renders `<Workbench workbench={workbench} />`.
294
-
295
295
  ## Nomenclature
296
296
 
297
- - **Workbench core**: the headless object created by `createWorkbenchCore()`. It owns the registries, controllers, and shared workbench state.
298
- - **Registry**: a typed collection of contributions. The workbench has registries for commands, keybindings, resources, layout (Panels, placeholders, menu items), renderers (Panel renderers and tree renderers), modes, navigation, notifications, and preferences.
299
- - **Controller**: a stateful slice of workbench UX exposed alongside the registries — breadcrumbs, command palette open/close state, focus, history, side-panel open/close state, and session-panel mode.
300
297
  - **Contribution**: a declarative unit added to a registry, such as a command, menu item, resource kind, Panel, renderer, tree renderer, mode, or KanbanView.
301
298
  - **Workbench module**: contribution owner registered with `workbench.registerModule(module)` and removed with `workbench.unregisterModule(moduleId)`. Module disposables are tracked and disposed together. See [Contribution Ownership](https://github.com/pufflyai/prompt-studio/blob/main/.pstdio/docs/references/workbench/contribution-ownership.md).
302
299
  - **Runtime extension**: extension metadata from `pstdio-extensions` that a host maps into workbench modules at the trust boundary.
303
- - **Workbench**: the React frame rendered by `Workbench`. It arranges the workbench regions, command palette, panels, and session surface from the workbench core only.
304
- - **Region**: a named layout target. See the Regions Overview table below.
305
- - **Panel contribution**: a registered view definition in the layout registry. Panels declare a region, a `rendererId`, and optional renderer-owned `config`.
306
300
  - **Panel placement**: an opened instance of a Panel contribution in a region. Placements track active Panel, resource URI, title, pinned/closable flags, and placement ownership.
307
301
  - **Tree renderer contribution**: a tree-shaped renderer registered under `renderers`. Provides `getBody` (sectioned body), optional `getFooter` (flat footer node list), and `getChildren` (lazy children). Auto-registers a Panel renderer with the same id so Panels place trees through `layout.registerPanel`.
308
302
  - **Kanban renderer contribution**: a data-workspace renderer registered under `renderers`. Provides a schema, `executeQuery(state)` rows, and row-mutation callbacks. Auto-registers a Panel renderer with the same id so Panels place the workspace through `layout.registerPanel`. The presentational layer is `<KanbanRenderer>` from `@pstdio/ui`.
@@ -321,7 +315,7 @@ A host normally creates one core, registers modules into it, and renders `<Workb
321
315
  - **Notification**: a transient workbench message emitted by workbench modules or extensions. Notifications can include command-backed actions and are rendered as workbench toast chrome.
322
316
  - **Side Panel**: the project-owned carry surface controlled by `workbench.sidePanel`. It can be `attached`, `floating`, or `closed` while preserving one live host and its resource binding.
323
317
 
324
- ## Regions Overview
318
+ ## Regions overview
325
319
 
326
320
  Workbench regions are named layout targets used by Panel contributions. They describe where a Panel belongs in the workbench; the workbench decides the exact chrome, tabs, resize handles, and visibility behavior.
327
321
 
@@ -348,7 +342,7 @@ Panels with tabs are paired with a `<panel>-header` region that the workbench re
348
342
 
349
343
  The command palette, toast notifications, and resize handles are workbench chrome, not workbench regions. Use the `RegionMap` Storybook story to see the current region placement rendered through the real `Workbench`.
350
344
 
351
- ## Header Actions
345
+ ## Header actions
352
346
 
353
347
  Each region header renders command-backed actions from two menu paths derived from `headerLeadingMenuPath(region)` and `headerTrailingMenuPath(region)`. Nav Chrome reuses these paths under `workbenchTopHeaderLeadingMenuPath` and `workbenchTopHeaderTrailingMenuPath`. It keeps history, the breadcrumb trail, the trailing breadcrumb-action slot, and Sidenav/Secondary/Side visibility controls mounted in that order. Workbench modules can register commands and add menu actions to those paths without moving navigation into a Panel Header.
354
348
 
@@ -0,0 +1,241 @@
1
+ const v = (e) => [.../* @__PURE__ */ new Set([e.region, ...e.allowedRegions ?? []])], _ = (e) => e.show ? Array.isArray(e.show) ? e.show : [e.show] : [], q = (e, o, s, n) => {
2
+ if (!o || s.resourceKind !== o.id) return !1;
3
+ const l = o.slots[s.slot];
4
+ return l ? s.extensionId !== o.extensionId && (!l.external || s.slot === "primary") ? (n.push({
5
+ code: "extension_resource_slot_closed",
6
+ message: `Slot "${s.slot}" is closed to external panels`,
7
+ panelId: s.panel,
8
+ slot: s.slot
9
+ }), !1) : e.composition.panels.some((r) => r.id === s.panel) ? !0 : (n.push({
10
+ code: "extension_panel_missing",
11
+ message: `Resource panel "${s.id}" names unknown panel "${s.panel}"`,
12
+ panelId: s.panel,
13
+ slot: s.slot
14
+ }), !1) : (n.push({
15
+ code: "extension_resource_slot_missing",
16
+ message: `Resource panel "${s.id}" names unknown slot "${s.slot}"`,
17
+ panelId: s.panel,
18
+ slot: s.slot
19
+ }), !1);
20
+ }, w = (e) => {
21
+ const o = e.kind?.slots[e.slotName];
22
+ if (!o)
23
+ return e.diagnostics.push({
24
+ code: "extension_resource_slot_missing",
25
+ message: `Mode "${e.input.mode.id}" places unknown slot "${e.slotName}"`,
26
+ slot: e.slotName
27
+ }), [];
28
+ const s = e.policy.required === !0;
29
+ return s && o.cardinality === "many" && e.diagnostics.push({
30
+ code: "extension_placement_required_invalid",
31
+ message: `Required slot "${e.slotName}" has cardinality many; name a specific panel instead`,
32
+ slot: e.slotName
33
+ }), e.validEdges.filter((n) => n.slot === e.slotName && !e.overriddenPanels.has(n.panel)).filter((n) => n.extensionId === e.kind?.extensionId).map((n) => ({
34
+ panelId: n.panel,
35
+ slot: e.slotName,
36
+ policy: e.policy,
37
+ required: s && o.cardinality === "one"
38
+ }));
39
+ }, P = (e, o) => {
40
+ const s = e.composition.resourceKinds.find((r) => r.id === e.context.resourceKind), n = _(o), l = n.find((r) => r.resourceKind === e.context.resourceKind);
41
+ if (l && o.extensionId === s?.extensionId) return l;
42
+ if (o.extensionId === e.mode.extensionId)
43
+ return n.find((r) => r.resourceKind === void 0);
44
+ }, b = (e, o, s) => {
45
+ const n = e.composition.resourceKinds.find((t) => t.id === e.context.resourceKind), l = [], r = e.composition.resourcePanels.filter((t) => q(e, n, t, s)), a = {
46
+ ...o?.panels ?? {},
47
+ ...e.mode.modePanels ?? {}
48
+ }, i = new Set(Object.keys(a));
49
+ for (const [t, c] of Object.entries(o?.slots ?? {}))
50
+ l.push(...w({
51
+ diagnostics: s,
52
+ kind: n,
53
+ input: e,
54
+ overriddenPanels: i,
55
+ policy: c,
56
+ slotName: t,
57
+ validEdges: r
58
+ }));
59
+ for (const t of e.composition.panels) {
60
+ const c = P(e, t);
61
+ if (!c) continue;
62
+ const p = a[t.id] ?? c;
63
+ l.push({
64
+ panelId: t.id,
65
+ policy: p,
66
+ base: c,
67
+ required: p.required ?? c.required ?? !1
68
+ });
69
+ }
70
+ const u = new Set(l.map((t) => t.panelId));
71
+ for (const [t, c] of Object.entries(a)) {
72
+ if (u.has(t)) continue;
73
+ const p = r.find((d) => d.panel === t);
74
+ if (!e.composition.panels.find((d) => d.id === t) || o?.panels?.[t] && !p) {
75
+ s.push({
76
+ code: "extension_panel_missing",
77
+ message: `Mode "${e.mode.id}" places panel "${t}" that is not registered for the resource`,
78
+ panelId: t
79
+ });
80
+ continue;
81
+ }
82
+ l.push({
83
+ panelId: t,
84
+ slot: p?.slot,
85
+ policy: c,
86
+ required: c.required === !0
87
+ });
88
+ }
89
+ return {
90
+ candidates: l,
91
+ validEdges: r
92
+ };
93
+ }, h = (e) => {
94
+ const o = e.base ? v(e.base) : v(e.policy);
95
+ return !e.base || !e.policy.allowedRegions ? o : v(e.policy).filter((s) => o.includes(s));
96
+ }, y = (e, o, s) => {
97
+ if (!e.composition.panels.find((r) => r.id === o.panelId)) return;
98
+ const l = h(o);
99
+ if (!l.includes(o.policy.region)) {
100
+ s.push({
101
+ code: "extension_panel_placement_unresolvable",
102
+ message: `Panel "${o.panelId}" cannot be placed in region "${o.policy.region}"`,
103
+ panelId: o.panelId
104
+ });
105
+ return;
106
+ }
107
+ return {
108
+ panelId: o.panelId,
109
+ region: o.policy.region,
110
+ slot: o.slot,
111
+ required: o.required,
112
+ defaultOpen: o.required || o.policy.defaultOpen !== !1,
113
+ pinned: o.policy.pinned,
114
+ allowedRegions: l,
115
+ origin: o.required ? "required" : "default"
116
+ };
117
+ }, g = (e, o, s) => {
118
+ const n = /* @__PURE__ */ new Map();
119
+ let l;
120
+ for (const r of o) {
121
+ const a = y(e, r, s);
122
+ if (a) {
123
+ n.has(a.panelId) || n.set(a.panelId, a);
124
+ continue;
125
+ }
126
+ r.required && h(r).includes("main") && (l = {
127
+ panelId: r.panelId
128
+ });
129
+ }
130
+ return {
131
+ requiredFallback: l,
132
+ resolvedByPanel: n
133
+ };
134
+ }, R = (e) => Object.entries(e.regions).filter((o) => !!o[1]), $ = (e) => {
135
+ for (const [o, s] of R(e.persisted)) {
136
+ for (const n of s.order) {
137
+ const l = e.resolvedByPanel.get(n);
138
+ l && e.place({
139
+ ...l,
140
+ origin: "persisted"
141
+ }, l.allowedRegions.includes(o) ? o : l.region);
142
+ }
143
+ s.activePanelId && e.placed.has(s.activePanelId) && (e.activePanelIds[o] = s.activePanelId);
144
+ }
145
+ }, K = (e, o) => {
146
+ const s = [], n = {}, l = {}, r = /* @__PURE__ */ new Set(), a = (i, u) => {
147
+ r.has(i.panelId) || (r.add(i.panelId), s.push({
148
+ ...i,
149
+ region: u
150
+ }), n[u] = [...n[u] ?? [], i.panelId]);
151
+ };
152
+ if (!e.persisted) {
153
+ for (const i of o.values()) i.defaultOpen && a(i, i.region);
154
+ return {
155
+ activePanelIds: l,
156
+ placed: r,
157
+ placements: s,
158
+ regionOrder: n
159
+ };
160
+ }
161
+ $({
162
+ activePanelIds: l,
163
+ persisted: e.persisted,
164
+ place: a,
165
+ placed: r,
166
+ resolvedByPanel: o
167
+ });
168
+ for (const i of o.values())
169
+ i.required && !r.has(i.panelId) && a(i, i.region);
170
+ return {
171
+ activePanelIds: l,
172
+ placed: r,
173
+ placements: s,
174
+ regionOrder: n
175
+ };
176
+ }, k = (e) => {
177
+ const o = [], s = e.context.resourceKind ? e.mode.resources?.[e.context.resourceKind] : void 0;
178
+ if (e.context.resourceKind && !s)
179
+ return o.push({
180
+ code: "extension_mode_resource_unsupported",
181
+ message: `Mode "${e.mode.id}" does not accept resource kind "${e.context.resourceKind}"`
182
+ }), {
183
+ placements: [],
184
+ regionOrder: {},
185
+ activePanelIds: {},
186
+ addablePanels: [],
187
+ diagnostics: o
188
+ };
189
+ const {
190
+ candidates: n,
191
+ validEdges: l
192
+ } = b(e, s, o), {
193
+ requiredFallback: r,
194
+ resolvedByPanel: a
195
+ } = g(e, n, o), {
196
+ activePanelIds: i,
197
+ placed: u,
198
+ placements: t,
199
+ regionOrder: c
200
+ } = K(e, a), p = Array.from(a.values()).filter((d) => !d.required && !u.has(d.panelId)).map(({
201
+ panelId: d,
202
+ region: m,
203
+ allowedRegions: f,
204
+ pinned: x
205
+ }) => ({
206
+ panelId: d,
207
+ region: m,
208
+ allowedRegions: f,
209
+ pinned: x
210
+ })), I = new Set(p.map((d) => d.panelId));
211
+ for (const d of l) {
212
+ if (u.has(d.panel) || I.has(d.panel)) continue;
213
+ const m = s?.panels?.[d.panel] ?? s?.slots?.[d.slot];
214
+ if (!m) continue;
215
+ const f = y(e, {
216
+ panelId: d.panel,
217
+ slot: d.slot,
218
+ policy: m,
219
+ required: !1
220
+ }, o);
221
+ f && (p.push({
222
+ panelId: f.panelId,
223
+ region: f.region,
224
+ allowedRegions: f.allowedRegions,
225
+ pinned: f.pinned
226
+ }), I.add(f.panelId));
227
+ }
228
+ return {
229
+ placements: t,
230
+ regionOrder: c,
231
+ activePanelIds: i,
232
+ addablePanels: p,
233
+ diagnostics: o,
234
+ requiredFallback: r
235
+ };
236
+ }, O = ["sidenav", "main", "secondary", "side"];
237
+ export {
238
+ O as d,
239
+ k as r
240
+ };
241
+ //# sourceMappingURL=composition-resolver-types-CWykPyEK.js.map