@hachej/boring-workspace 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 (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +94 -0
  3. package/dist/CodeEditor-DQqOn4xz.js +266 -0
  4. package/dist/CommandPalette-aM61U-b0.js +5229 -0
  5. package/dist/FileTree-DRq_bfue.js +245 -0
  6. package/dist/MarkdownEditor-DjiHxnRv.js +349 -0
  7. package/dist/WorkspaceLoadingState-By0dZoPD.js +568 -0
  8. package/dist/agent-tool-NvxKfist.d.ts +28 -0
  9. package/dist/app-front.d.ts +485 -0
  10. package/dist/app-front.js +452 -0
  11. package/dist/app-server.d.ts +53 -0
  12. package/dist/app-server.js +769 -0
  13. package/dist/bootstrapServer-BRUqUpVW.d.ts +66 -0
  14. package/dist/boring-workspace.css +1 -0
  15. package/dist/charts.d.ts +114 -0
  16. package/dist/charts.js +143 -0
  17. package/dist/events.d.ts +178 -0
  18. package/dist/events.js +88 -0
  19. package/dist/explorer-DtLUnuah.d.ts +129 -0
  20. package/dist/panel-DnvDNQac.js +6 -0
  21. package/dist/server.d.ts +84 -0
  22. package/dist/server.js +811 -0
  23. package/dist/shared.d.ts +113 -0
  24. package/dist/shared.js +11 -0
  25. package/dist/testing-e2e.d.ts +68 -0
  26. package/dist/testing-e2e.js +45 -0
  27. package/dist/testing.d.ts +464 -0
  28. package/dist/testing.js +10984 -0
  29. package/dist/utils-B6yFEsav.js +8 -0
  30. package/dist/workspace.css +5780 -0
  31. package/dist/workspace.d.ts +2119 -0
  32. package/dist/workspace.js +1884 -0
  33. package/docs/INTERFACES.md +58 -0
  34. package/docs/PLUGIN_STRUCTURE.md +162 -0
  35. package/docs/README.md +19 -0
  36. package/docs/bridge.md +135 -0
  37. package/docs/panels.md +102 -0
  38. package/docs/plans/GENERIC_EXPLORER_PLUGIN_PLAN.md +455 -0
  39. package/docs/plans/MACRO_PLUGIN_GENERIC_HELPERS_AUDIT.md +962 -0
  40. package/docs/plans/PLUGIN_OUTPUTS_ISOLATION_PLAN.md +301 -0
  41. package/docs/plans/README.md +9 -0
  42. package/docs/plans/UI_BRIDGE_OWNERSHIP_REFACTOR.md +303 -0
  43. package/docs/plans/archive/CODE_OWNERSHIP_CLEANUP_PLAN.md +387 -0
  44. package/docs/plans/archive/COMMAND_PALETTE_REGISTRY.md +814 -0
  45. package/docs/plans/archive/DECLARATIVE_LAYOUT_MIGRATION.md +277 -0
  46. package/docs/plans/archive/PLUGIN_MODEL.md +3674 -0
  47. package/docs/plans/archive/SRC_FOLDER_REORG_PLAN.md +307 -0
  48. package/docs/plans/archive/UNIFIED_EVENT_BUS.md +647 -0
  49. package/docs/plans/archive/WORKSPACE_V2_PLAN.md +2489 -0
  50. package/docs/plugins.md +158 -0
  51. package/package.json +164 -0
@@ -0,0 +1,58 @@
1
+ # Workspace Interfaces
2
+
3
+ Last updated: 2026-05-02
4
+
5
+ `@boring/workspace` is a workspace UI and bridge package. The app shell owns
6
+ auth, routing, application persistence, and the concrete chat component.
7
+ Workspace owns layout runtime, layout preferences, plugin registries, bridge
8
+ commands, and default workspace plugins.
9
+
10
+ ## Package Boundaries
11
+
12
+ - `src/front/` hosts React providers, layouts, Dockview chrome, registries,
13
+ bridge clients, and generic UI.
14
+ - `src/plugins/` hosts plugin-owned domain behavior. Plugin code is split by
15
+ layer: `front/`, `server/`, and `shared/`.
16
+ - `src/server/` hosts workspace UI bridge routes, UI tools, and server plugin
17
+ bootstrap helpers.
18
+ - `src/shared/` hosts browser-safe contracts only. No `node:*`, no `Buffer`,
19
+ and no agent package imports.
20
+ - `src/app/` hosts front/server composition helpers such as
21
+ `WorkspaceAgentFront` and `createWorkspaceAgentServer`, where workspace app
22
+ code may compose with documented `@boring/agent/server` APIs.
23
+
24
+ ## Core Contracts
25
+
26
+ - Plugin outputs: `src/shared/plugins/types.ts`
27
+ - `panel`, `left-tab`, `command`, `catalog`, `binding`, `provider`,
28
+ `surface-resolver`, and `agent-tool`.
29
+ - Surface opening: `src/shared/types/surface.ts`
30
+ - `SurfaceOpenRequest { kind, target, meta }` is resolved by plugin
31
+ `surface-resolver` outputs into panel openings.
32
+ - UI bridge: `src/shared/ui-bridge.ts`
33
+ - Agents and servers post `UiCommand` values. The front-end dispatches them
34
+ against the workspace runtime.
35
+ - Filesystem data: `src/plugins/filesystemPlugin/front/data`
36
+ - Filesystem client, hooks, event stream, and cache invalidation are plugin
37
+ owned.
38
+ - Data catalog: `src/plugins/dataCatalogPlugin/front` and
39
+ `src/plugins/dataCatalogPlugin/server`
40
+ - Catalog rows are opened through `openSurface`; row-to-panel mapping belongs
41
+ to the plugin resolver.
42
+ - Server plugins: `src/server/plugins`
43
+ - `defineServerPlugin()` validates tools, routes, provisioning, and native Pi
44
+ package declarations.
45
+ - `composeServerPlugins()` combines smaller server plugin fragments.
46
+ - `piPackages` are passed to `@boring/agent` as in-memory Pi settings, so
47
+ workspace adapters can depend on native Pi packages without requiring
48
+ Boring-specific exports from those packages.
49
+
50
+ ## Ownership Rules
51
+
52
+ - Workspace chrome must not hardcode plugin panel ids or plugin domain rules.
53
+ - Plugin data APIs stay under the owning plugin; there is no `front/data`
54
+ compatibility layer.
55
+ - Use `openSurface` for domain targets that need resolver selection.
56
+ - Use `openPanel` only when the caller intentionally names the concrete panel.
57
+ - Front/shared workspace code does not value-import `@boring/agent`; app/server
58
+ composition may import documented agent server APIs.
@@ -0,0 +1,162 @@
1
+ # Workspace Plugin Structure
2
+
3
+ Last updated: 2026-05-01
4
+
5
+ Workspace plugins use a small, predictable folder shape. The goal is
6
+ ownership clarity, not ceremony: plugin domain behavior stays in the plugin,
7
+ and workspace core stays a generic host.
8
+
9
+ See `INTERFACES.md` for the package-level contracts these plugins contribute
10
+ to.
11
+
12
+ ## Standard Layout
13
+
14
+ Start from `packages/workspace/templates/plugin/` when creating a new plugin,
15
+ then delete the files the plugin does not need.
16
+
17
+ ```txt
18
+ <plugin>/
19
+ front/
20
+ index.tsx # front plugin factory and public front exports
21
+ panels.tsx # panel definitions
22
+ catalogs.ts # catalog outputs/config helpers
23
+ surfaceResolver.ts # openSurface target -> panel resolution
24
+ bindings.tsx # React side-effect bindings, if needed
25
+ data/ # plugin-owned client data API/hooks/cache
26
+ server/
27
+ index.ts # server plugin factory and public server exports
28
+ tools.ts # agent tools, if needed
29
+ routes.ts # server routes, if needed
30
+ shared/
31
+ constants.ts # plugin id, catalog ids, surface kinds
32
+ types.ts # platform-neutral shared types, if needed
33
+ ```
34
+
35
+ Use only the files a plugin actually needs. Large component families may live
36
+ in subfolders such as `file-tree/`, `code-editor/`, or `empty-file-panel/`.
37
+
38
+ ## Ownership Rules
39
+
40
+ - `front/index.tsx` composes front outputs; large behavior belongs in focused files.
41
+ - `server/index.ts` composes server outputs with `defineServerPlugin()`.
42
+ - Shared files must stay platform-neutral. Do not import plugin `front/` or
43
+ `server/` code from `shared/`.
44
+ - Domain search belongs in `front/catalogs.ts`.
45
+ - Domain open behavior belongs in `front/surfaceResolver.ts`.
46
+ - Domain event names live in `shared/events.ts` when both layers need the
47
+ contract; event names must be keyed by plugin id.
48
+ - Plugin data clients/hooks live in `front/data/`, not package `front/data`.
49
+ - Server prompts/tools/routes/provisioning live under `server/`, not mixed
50
+ with client code.
51
+ - Workspace core may host registries, providers, event transport, and bridge
52
+ dispatch only. It must not hardcode plugin panel ids or plugin domain rules.
53
+ - Catalog selection and plugin-owned routing should prefer `openSurface`.
54
+ `openPanel` remains available for explicit app-level panel opens.
55
+ - Executable agent tools should be server plugin contributions. The legacy
56
+ front `agentTools` field remains for migration only.
57
+
58
+ ## Composed Plugins
59
+
60
+ Use `composePlugins()` when a front plugin is easier to build from smaller
61
+ front fragments. The composed plugin flattens child panels, commands,
62
+ catalogs, bindings, and outputs into one normal `WorkspaceFrontPlugin`.
63
+
64
+ Default behavior adopts child ownership to the parent plugin id. Use
65
+ `adoptOutputs: false` only when registry ownership must stay attached to the
66
+ child fragment for diagnostics or selective unregister behavior.
67
+
68
+ ```ts
69
+ const macroPlugin = composePlugins({
70
+ id: "boring-macro",
71
+ label: "Macro",
72
+ plugins: [macroPanelsPlugin, macroSurfacesPlugin, macroSeriesExplorerPlugin],
73
+ })
74
+ ```
75
+
76
+ Use `composeServerPlugins()` for the matching server side. It concatenates
77
+ child tools, prompt text, pi package declarations, provisioning, and routes
78
+ into one normal `WorkspaceServerPlugin`.
79
+
80
+ ```ts
81
+ const macroServerPlugin = composeServerPlugins({
82
+ id: "boring-macro",
83
+ label: "Macro",
84
+ plugins: [macroToolsPlugin, macroRoutesPlugin],
85
+ })
86
+ ```
87
+
88
+ ## Pi Package Adapters
89
+
90
+ Treat pi packages as implementation dependencies. The app-facing contract is a
91
+ workspace adapter plugin that wraps the pi package and optionally adds front
92
+ integration.
93
+
94
+ Do not require pi packages to export Boring-specific adapters. The pi ecosystem
95
+ already has its own shape, such as `package.json` `pi.extensions` entries and
96
+ extension functions that call `pi.registerCommand(...)`. Workspace adapters
97
+ should adapt to that shape.
98
+
99
+ Declare native pi package dependencies on the server plugin:
100
+
101
+ ```ts
102
+ defineServerPlugin({
103
+ id: "markdown-preview",
104
+ piPackages: ["npm:pi-markdown-preview@0.9.7"],
105
+ })
106
+ ```
107
+
108
+ Workspace passes these declarations to `@boring/agent` as in-memory Pi
109
+ settings. This enables Pi's native package loader without mutating
110
+ `.pi/settings.json`.
111
+
112
+ ```txt
113
+ src/plugins/markdownPreview/
114
+ shared/
115
+ constants.ts # workspace ids, surface kinds, command names
116
+ server/
117
+ index.ts # defineServerPlugin(), pi package dependency wrapper
118
+ routes.ts # optional workspace-native render/preview routes
119
+ front/
120
+ index.tsx # defineFrontPlugin()
121
+ panels.tsx # workspace-native preview panel
122
+ surfaceResolver.ts # markdown-preview.open -> panel resolution
123
+ ```
124
+
125
+ For example, a wrapper around `pi-markdown-preview` can depend on that package,
126
+ read or invoke its pi extension behavior on the server side, and expose a
127
+ workspace-native `markdown-preview.open` surface on the front side. The agent
128
+ prompt should teach the model to use workspace `openSurface` when a Boring app
129
+ is present, even if the underlying pi package also provides terminal/browser
130
+ slash commands.
131
+
132
+
133
+ ## Current Plugins
134
+
135
+ - `packages/workspace/src/plugins/filesystemPlugin`
136
+ - `packages/workspace/src/plugins/dataCatalogPlugin`
137
+ - `apps/boring-macro-v2/src/plugins/macro`
138
+ - `apps/workspace-playground/src/plugins/playgroundDataCatalog`
139
+
140
+ ## Invariants
141
+
142
+ Run:
143
+
144
+ ```sh
145
+ pnpm --filter @boring/workspace run lint:plugin-invariants
146
+ ```
147
+
148
+ The scan rejects:
149
+
150
+ - `filePatterns`, `fileFallback`, `PanelRegistry.resolve`, and file-handler
151
+ routing metadata in source.
152
+ - `front/data` imports or path references in source.
153
+ - `@boring/agent` imports from `workspace/src/shared/plugins`.
154
+ - `front`/`server` imports from production `workspace/src/shared/plugins`.
155
+ - Production plugin `front/`, `server/`, and `shared/` layers importing across
156
+ the wrong layer boundary.
157
+ - TypeScript source files directly under a plugin root instead of
158
+ `front/`, `server/`, or `shared/`.
159
+ - Plugin-domain imports from production `workspace/src/front/chrome`, `events`,
160
+ and `hooks`.
161
+ - Legacy plugin file names such as `catalog.ts`, `surfaceTargets.ts`,
162
+ root-level `client.ts`, or root-level `server.ts`.
package/docs/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # Workspace Docs
2
+
3
+ Current package docs live here. Active ownership plans stay in `docs/plans/`.
4
+ Older implementation plans live in `docs/plans/archive/`.
5
+
6
+ ## Current References
7
+
8
+ - `INTERFACES.md` - package boundaries and public abstractions.
9
+ - `PLUGIN_STRUCTURE.md` - plugin folder shape and invariant scans.
10
+ - `plans/PLUGIN_OUTPUTS_ISOLATION_PLAN.md` - latest plugin ownership plan.
11
+ - `plans/UI_BRIDGE_OWNERSHIP_REFACTOR.md` - UI bridge ownership decision.
12
+ - `plans/archive/` - superseded implementation plans.
13
+
14
+ ## Rules
15
+
16
+ - Keep root package markdown to `README.md` and `CHANGELOG.md`.
17
+ - Prefer short current-reference docs over long status reports.
18
+ - Move old plans into `plans/archive/` instead of keeping them beside active
19
+ ownership plans.
package/docs/bridge.md ADDED
@@ -0,0 +1,135 @@
1
+ > boring-ui agents use exec_ui to open panels and interact with the workspace. Ask boring-ui to wire up a new surface.
2
+
3
+ # UI Bridge
4
+
5
+ The UI bridge is the typed pubsub channel between the agent backend and the workspace frontend. The agent calls `exec_ui` (a tool) to post commands; the frontend dispatches them against the live workspace runtime.
6
+
7
+ ## Table of Contents
8
+
9
+ - [Opening a panel from the agent](#opening-a-panel-from-the-agent)
10
+ - [openSurface vs openPanel](#opensurface-vs-openpanel)
11
+ - [Reading current UI state](#reading-current-ui-state)
12
+ - [Surface resolvers](#surface-resolvers)
13
+ - [Posting from server code](#posting-from-server-code)
14
+ - [Frontend event bus](#frontend-event-bus)
15
+
16
+ ---
17
+
18
+ ## Opening a panel from the agent
19
+
20
+ Use `exec_ui` with `kind: "openSurface"`:
21
+
22
+ ```json
23
+ {
24
+ "kind": "openSurface",
25
+ "params": {
26
+ "kind": "my-plugin.open",
27
+ "target": "item-123",
28
+ "meta": { "title": "My Item" }
29
+ }
30
+ }
31
+ ```
32
+
33
+ The workspace routes this to the plugin's `surface-resolver` output, which maps the `kind` to a concrete panel open call.
34
+
35
+ ---
36
+
37
+ ## openSurface vs openPanel
38
+
39
+ | method | use when |
40
+ |---|---|
41
+ | `openSurface` | you want plugin resolver selection — preferred for domain targets |
42
+ | `openPanel` | you intentionally name the concrete panel id |
43
+
44
+ **Prefer `openSurface`.** It keeps the agent decoupled from panel ids and lets the plugin control routing.
45
+
46
+ Open a file in the editor (built-in surface):
47
+
48
+ ```json
49
+ {
50
+ "kind": "openSurface",
51
+ "params": {
52
+ "kind": "workspace.open.path",
53
+ "target": "src/index.ts"
54
+ }
55
+ }
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Reading current UI state
61
+
62
+ Use `get_ui_state` before `openPanel` to discover which panel components are registered, or to check what the user is currently viewing:
63
+
64
+ ```json
65
+ // tool call: get_ui_state (no params)
66
+ // returns:
67
+ {
68
+ "workbenchOpen": true,
69
+ "drawerOpen": false,
70
+ "openTabs": [{ "id": "...", "title": "...", "params": {} }],
71
+ "activeTab": "tab-id-or-null",
72
+ "activeFile": "src/index.ts-or-null",
73
+ "availablePanels": ["code-editor", "chart-canvas", "..."]
74
+ }
75
+ ```
76
+
77
+ `availablePanels` lists every component id registered by the host — use these with `exec_ui openPanel`.
78
+
79
+ ---
80
+
81
+ ## Surface resolvers
82
+
83
+ Register a surface resolver in your plugin to map `SurfaceOpenRequest` kinds to panel opens:
84
+
85
+ ```ts
86
+ import { defineFrontPlugin, type SurfacePanelResolution } from '@boring/workspace'
87
+
88
+ defineFrontPlugin({
89
+ outputs: [
90
+ {
91
+ type: 'surface-resolver',
92
+ resolve(req): SurfacePanelResolution | null {
93
+ if (req.kind === 'my-plugin.open') {
94
+ return {
95
+ panelId: 'my-panel',
96
+ params: { id: req.target },
97
+ }
98
+ }
99
+ return null
100
+ },
101
+ },
102
+ ],
103
+ })
104
+ ```
105
+
106
+ ---
107
+
108
+ ## Posting from server code
109
+
110
+ From a Fastify route or server plugin:
111
+
112
+ ```ts
113
+ import { postUiCommand } from '@boring/workspace/server'
114
+
115
+ await postUiCommand(workspaceId, {
116
+ kind: 'openSurface',
117
+ params: { kind: 'my-plugin.open', target: 'item-123' },
118
+ })
119
+ ```
120
+
121
+ ---
122
+
123
+ ## Frontend event bus
124
+
125
+ Subscribe to workspace events on the frontend:
126
+
127
+ ```ts
128
+ import { events, workspaceEvents } from '@boring/workspace'
129
+
130
+ events.on(workspaceEvents.panelOpened, (panel) => {
131
+ console.log('panel opened', panel.id)
132
+ })
133
+ ```
134
+
135
+ See [plugins.md](./plugins.md) to register a surface resolver in your plugin.
package/docs/panels.md ADDED
@@ -0,0 +1,102 @@
1
+ > boring-ui can create panel components. Ask it to build one for your domain.
2
+
3
+ # Panels
4
+
5
+ Panels are React components rendered inside the workspace dockview layout. They receive `PaneProps<T>` and can be opened programmatically by the agent or user.
6
+
7
+ ## Table of Contents
8
+
9
+ - [Defining a panel](#defining-a-panel)
10
+ - [Panel component API](#panel-component-api)
11
+ - [Placement](#placement)
12
+ - [Auto-lazy loading](#auto-lazy-loading)
13
+ - [Opening panels](#opening-panels)
14
+
15
+ ---
16
+
17
+ ## Defining a panel
18
+
19
+ ```ts
20
+ import { definePanel } from '@boring/workspace'
21
+
22
+ export const myPanel = definePanel({
23
+ id: 'my-panel',
24
+ title: 'My Panel',
25
+ placement: 'center',
26
+ // Zero-arg factory → auto-detected as lazy (code-split)
27
+ component: () => import('./MyPane').then(m => ({ default: m.MyPane })),
28
+ })
29
+ ```
30
+
31
+ ---
32
+
33
+ ## Panel component API
34
+
35
+ Panel components receive `PaneProps<T>`:
36
+
37
+ ```ts
38
+ import type { PaneProps } from '@boring/workspace'
39
+
40
+ interface Params { id?: string; query?: string }
41
+
42
+ export function MyPane({ params, api, containerApi }: PaneProps<Params>) {
43
+ // params — data passed when the panel is opened
44
+ // api — DockviewPanelApi (close, setTitle, onDidParametersChange, …)
45
+ // containerApi — DockviewApi (addPanel, fromJSON, …)
46
+ }
47
+ ```
48
+
49
+ React to parameter changes (agent re-opens with new params):
50
+
51
+ ```ts
52
+ useEffect(() => {
53
+ const disposable = api.onDidParametersChange(() => {
54
+ // params updated
55
+ })
56
+ return () => disposable.dispose()
57
+ }, [api])
58
+ ```
59
+
60
+ ---
61
+
62
+ ## Placement
63
+
64
+ | value | where |
65
+ |---|---|
66
+ | `center` | main editor area |
67
+ | `right` | right sidebar |
68
+ | `bottom` | bottom panel |
69
+
70
+ ---
71
+
72
+ ## Auto-lazy loading
73
+
74
+ **Do not set `lazy: true`.** The registry auto-detects it:
75
+
76
+ - Zero-arg function `() => import(...)` → lazy (code-split, loaded on first open)
77
+ - Component `(props) => JSX` → eager (loaded at startup)
78
+
79
+ ---
80
+
81
+ ## Opening panels
82
+
83
+ From the agent via exec_ui (see [bridge.md](./bridge.md)):
84
+
85
+ ```json
86
+ {
87
+ "kind": "openSurface",
88
+ "params": { "kind": "my-plugin.open", "target": "item-123" }
89
+ }
90
+ ```
91
+
92
+ From React code directly:
93
+
94
+ ```ts
95
+ containerApi.addPanel({
96
+ id: 'my-panel',
97
+ component: 'my-panel',
98
+ params: { id: 'item-123' },
99
+ })
100
+ ```
101
+
102
+ See [plugins.md](./plugins.md) for the full plugin API.