@hachej/boring-workspace 0.1.40 → 0.1.42

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 +26 -261
  2. package/dist/{FileTree-DNIzusWa.js → FileTree-CVsvICGP.js} +1 -1
  3. package/dist/MarkdownEditor-BvaGmzWP.js +582 -0
  4. package/dist/{WorkspaceLoadingState-EratTJfG.js → WorkspaceLoadingState-yp4vNmrT.js} +21 -22
  5. package/dist/{WorkspaceProvider-uuxyAx3i.js → WorkspaceProvider-DkZAxsYo.js} +2902 -2741
  6. package/dist/app-front.d.ts +5 -0
  7. package/dist/app-front.js +221 -217
  8. package/dist/app-server.d.ts +2 -2
  9. package/dist/app-server.js +23 -5
  10. package/dist/{createInMemoryBridge-DSjZ9efK.d.ts → createInMemoryBridge-siFWq_R_.d.ts} +8 -0
  11. package/dist/plugin.d.ts +4 -2
  12. package/dist/server.d.ts +3 -3
  13. package/dist/server.js +23 -5
  14. package/dist/shared.d.ts +1 -1
  15. package/dist/{surface-obE7YwJk.d.ts → surface-DmIalUmP.d.ts} +2 -0
  16. package/dist/testing.d.ts +2 -0
  17. package/dist/testing.js +1 -1
  18. package/dist/workspace.css +112 -16
  19. package/dist/workspace.d.ts +8 -2
  20. package/dist/workspace.js +5 -5
  21. package/docs/INTERFACES.md +1 -1
  22. package/docs/PLUGIN_STRUCTURE.md +3 -3
  23. package/docs/PLUGIN_SYSTEM.md +46 -4
  24. package/docs/README.md +85 -22
  25. package/package.json +4 -4
  26. package/dist/MarkdownEditor-DhVfKSAq.js +0 -549
  27. /package/docs/plans/{ASK_USER_QUESTIONS_PLUGIN_SPEC.md → archive/ASK_USER_QUESTIONS_PLUGIN_SPEC.md} +0 -0
  28. /package/docs/plans/{FULL_PAGE_PANEL_ROUTE_SPEC.md → archive/FULL_PAGE_PANEL_ROUTE_SPEC.md} +0 -0
  29. /package/docs/plans/{GENERIC_EXPLORER_PLUGIN_PLAN.md → archive/GENERIC_EXPLORER_PLUGIN_PLAN.md} +0 -0
  30. /package/docs/plans/{PANE_TO_AGENT_CHAT_ACTIONS_SPEC.md → archive/PANE_TO_AGENT_CHAT_ACTIONS_SPEC.md} +0 -0
  31. /package/docs/plans/{PLUGIN_OUTPUTS_ISOLATION_PLAN.md → archive/PLUGIN_OUTPUTS_ISOLATION_PLAN.md} +0 -0
  32. /package/docs/plans/{README.md → archive/README.md} +0 -0
  33. /package/docs/plans/{UI_BRIDGE_OWNERSHIP_REFACTOR.md → archive/UI_BRIDGE_OWNERSHIP_REFACTOR.md} +0 -0
package/README.md CHANGED
@@ -7,56 +7,37 @@
7
7
 
8
8
  </div>
9
9
 
10
- Plugin system, panel registry, Dockview-based IDE layout, and typed agent-to-browser bridge for boring-ui apps. Everything the user touches — chat, files, catalogs, custom panes — composes here.
10
+ The workspace UI, layout, plugin, and bridge layer for boring-ui apps. It gives you
11
+ an IDE-style workbench — Dockview panes, a plugin system that contributes panels,
12
+ tabs, commands, catalogs, and surface resolvers, and a typed agent-to-browser
13
+ bridge. You write the domain logic; the workspace handles the chrome. The app shell
14
+ injects the chat component and owns auth, routing, and persistence.
11
15
 
12
16
  ```bash
13
17
  pnpm add @hachej/boring-workspace
14
18
  ```
15
19
 
16
- ---
17
-
18
- ## TL;DR
19
-
20
- **The Problem**: Agent apps need more than a chat window. You want file trees, editors, data tables, custom panes, keyboard shortcuts — but wiring all that chrome yourself means fighting layout engines, managing panel lifecycles, and building bridges between backend commands and frontend actions.
21
-
22
- **The Solution**: `@hachej/boring-workspace` gives you a complete IDE-style workbench with a plugin system that contributes panes, tabs, commands, catalogs, surface resolvers, and React context. The agent backend talks to the UI through a typed pubsub bus. You write the domain logic; the workspace handles the chrome.
23
-
24
- ### Why Use @hachej/boring-workspace?
25
-
26
- | Feature | What It Does |
27
- |---------|--------------|
28
- | **Plugin system** | Contribute panels, left-tabs, commands, catalogs, surface resolvers, and React bindings through a single manifest |
29
- | **Dockview layout** | Split, resize, drag, and dock panels — VS Code–style behavior out of the box |
30
- | **Auto code-splitting** | Lazy panels (`() => import(...)`) are automatically wrapped in `React.lazy + Suspense + ErrorBoundary` |
31
- | **Typed UI bridge** | Agent calls `exec_ui({ kind: "openFile", params })` → panel opens in the workbench. SSE + HTTP fallback. |
32
- | **Surface resolver** | Map agent-emitted `SurfaceOpenRequest` to panel opens — domain-specific routing without hardcoding panel IDs |
33
- | **Built-in plugins** | File tree, editor, command palette, session management — ready on mount |
34
- | **Composable** | Three levels: full layout (`IdeLayout`), provider + layout primitives, or headless hooks — pick what you need |
35
-
36
- ---
37
-
38
- ## Quick Example
20
+ ## Usage
39
21
 
40
22
  ```tsx
41
23
  import { WorkspaceProvider, IdeLayout } from "@hachej/boring-workspace"
42
24
  import { ChatPanel } from "@hachej/boring-agent"
43
25
 
44
- // Full shell — plug in your chat and plugins
45
26
  function App() {
46
27
  return (
47
- <WorkspaceProvider chatPanel={ChatPanel} workspaceId="default" plugins={[myPanelPlugin, dataCatalogPlugin]}>
28
+ <WorkspaceProvider chatPanel={ChatPanel} workspaceId="default" plugins={[myPlugin]}>
48
29
  <IdeLayout />
49
30
  </WorkspaceProvider>
50
31
  )
51
32
  }
52
33
  ```
53
34
 
54
- Add a panel in 8 lines:
35
+ Add a panel with a plugin:
55
36
 
56
37
  ```ts
57
38
  import { definePlugin } from "@hachej/boring-workspace/plugin"
58
39
 
59
- export const myPanelPlugin = definePlugin({
40
+ export const myPlugin = definePlugin({
60
41
  id: "my-panel",
61
42
  label: "My Panel",
62
43
  panels: [
@@ -70,255 +51,39 @@ export const myPanelPlugin = definePlugin({
70
51
  })
71
52
  ```
72
53
 
73
- ---
74
-
75
- ## Installation
76
-
77
- ```bash
78
- # pnpm
79
- pnpm add @hachej/boring-workspace
80
-
81
- # npm
82
- npm install @hachej/boring-workspace
83
-
84
- # from source
85
- git clone https://github.com/hachej/boring-ui.git
86
- cd boring-ui && pnpm install
87
- pnpm --filter @hachej/boring-workspace build
88
- ```
89
-
90
- ---
91
-
92
- ## Architecture
93
-
94
- ### Plugin System
54
+ `WorkspaceProvider` requires `chatPanel` and `workspaceId`; `plugins`, `apiBaseUrl`,
55
+ `authHeaders`, and `layoutPreferences` are optional. Lazy panel factories
56
+ (`() => import(...)`) are auto-wrapped in `React.lazy + Suspense + ErrorBoundary` —
57
+ do not set `lazy: true`.
95
58
 
96
- Plugins bootstrap into the workspace through a single `registerPlugin` call. `WorkspaceProvider` creates a `PanelRegistry`, calls `bootstrap()` with all plugins, and the registry auto-wraps lazy components.
97
-
98
- ```
99
- WorkspaceProvider
100
- ├── bootstrap(plugins)
101
- │ ├── registry.register(panel outputs)
102
- │ ├── registry.register(left-tab outputs)
103
- │ └── registry.register(command outputs)
104
-
105
- ├── DockviewShell
106
- │ ├── registry.getComponents() → lazy-wrapped panels
107
- │ └── DockviewReact (layout chrome)
108
-
109
- ├── UiBridgeClient
110
- │ ├── SSE command stream
111
- │ └── HTTP poll fallback
112
-
113
- └── Layout (IdeLayout / ChatLayout / ResponsiveDockviewShell)
114
- ```
59
+ ## Package surfaces
115
60
 
116
- ### UI Bridge
117
-
118
- ```
119
- Agent Backend Frontend
120
- ────────────── ────────
121
- POST /api/v1/ui/commands ──► UiBridge dispatches
122
- { kind, params } │ ├── openFile
123
- │ ├── openPanel
124
- │ └── showNotification
125
- ```
126
-
127
- Agent plugins emit `UiCommand` values on the server. The workbench has a typed event bus that dispatches them to the right handler.
128
-
129
- ```
130
- POST /api/v1/ui/commands
131
- { kind: "openFile", params: { path: "src/index.ts" } }
132
-
133
-
134
-
135
- UiBridgeClient receives → dispatches to FileTree plugin → expands node + focuses editor
136
- ```
137
-
138
- ### Built-in Plugins
139
-
140
- | Plugin | What It Adds |
141
- |--------|-------------|
142
- | Filesystem plugin | File tree (left tab), editor (center panel), file navigation |
143
- | Chat plugin | Integrates the injected `ChatPanel` into the layout |
144
- | Command palette | `⌘K`-driven search across commands, panels, and surfaces |
145
- | Session plugin | Lightweight session toolbar (current session, dropdown, new chat) |
146
-
147
- ---
148
-
149
- ## Package Surfaces
150
-
151
- | Import | Environment | What You Get |
152
- |--------|-------------|--------------|
153
- | `@hachej/boring-workspace` | Browser | `WorkspaceProvider`, `IdeLayout`, built-in layout primitives |
154
- | `@hachej/boring-workspace/plugin` | Browser | `definePlugin()` and browser-safe plugin authoring types |
155
- | `@hachej/boring-workspace/server` | Node | `defineServerPlugin()`, server routes, UI tools, Pi package helpers |
61
+ | Import | Environment | What you get |
62
+ | --- | --- | --- |
63
+ | `@hachej/boring-workspace` | Browser | `WorkspaceProvider`, `IdeLayout`, `ChatLayout`, layout primitives |
64
+ | `@hachej/boring-workspace/plugin` | Browser | `definePlugin()` and browser-safe authoring types |
65
+ | `@hachej/boring-workspace/server` | Node | `defineServerPlugin()`, server routes, UI tools, Pi helpers |
156
66
  | `@hachej/boring-workspace/shared` | Any | `PaneProps`, `SurfaceOpenRequest`, `UiCommand`, plugin types |
157
67
  | `@hachej/boring-workspace/events` | Any | Typed event bus for bridge communication |
158
- | `@hachej/boring-workspace/charts` | Browser | Recharts wrappers for data visualization |
68
+ | `@hachej/boring-workspace/charts` | Browser | Recharts wrappers |
159
69
  | `@hachej/boring-workspace/testing` | Browser | Test utilities and mock providers |
160
70
  | `@hachej/boring-workspace/app/front` | Browser | App composition: `WorkspaceAgentFront` |
161
71
  | `@hachej/boring-workspace/app/server` | Node | App composition: `createWorkspaceAgentServer` |
162
72
  | `@hachej/boring-workspace/globals.css` | Browser | Global CSS for the workspace chrome |
163
73
 
164
- ---
165
-
166
- ## Plugin Output Types
167
-
168
- | Output Type | Contributed Surface | Example |
169
- |-------------|--------------------|---------|
170
- | `panel` | Center/right/bottom pane | Code editor, data table, settings page |
171
- | `left-tab` | Persistent sidebar tab | File tree, data catalog, status panel |
172
- | `command` | Command palette entry | "Toggle dark mode", "Format files" |
173
- | `catalog` | Searchable data explorer | Customer list with faceted filters |
174
- | `surface-resolver` | Maps `exec_ui` → panel | `openFile` → editor panel with path |
175
- | `binding` | React context in provider tree | Theme, auth, workspace-scoped state |
176
- | `provider` | Binding + `apiBaseUrl` injection | Server-side plugin config passed to front |
177
- | `agent-tool` | Static agent tool via server plugin | `deploy`, `test`, `lint` commands |
178
-
179
- ### Writing a Plugin
180
-
181
- ```ts
182
- import { definePlugin } from "@hachej/boring-workspace/plugin"
183
- import type { PaneProps } from "@hachej/boring-workspace"
184
-
185
- export const statusPlugin = definePlugin({
186
- id: "build-status",
187
- label: "Build Status",
188
- leftTabs: [
189
- {
190
- id: "build-status-tab",
191
- title: "Build",
192
- panelId: "build-status-tab",
193
- component: () => import("./StatusTab").then(m => ({ default: m.StatusTab })),
194
- },
195
- ],
196
- panels: [
197
- {
198
- id: "build-details",
199
- label: "Build Details",
200
- placement: "bottom",
201
- component: () => import("./BuildDetails").then(m => ({ default: m.BuildDetails })),
202
- },
203
- ],
204
- })
205
-
206
- // Panel components receive PaneProps<T>:
207
- function StatusTab({ params, api, containerApi }: PaneProps<{}>) {
208
- // params — data when panel is opened
209
- // api — DockviewPanelApi (close, setTitle, …)
210
- // containerApi — DockviewApi (addPanel, layout, …)
211
- }
212
- ```
213
-
214
- ### Server Plugins
74
+ ## Documentation
215
75
 
216
- Server plugins are boot-time/static composition. Hot-reloadable `.pi/extensions` agent tools should use Pi extensions instead.
76
+ See [`docs/README.md`](./docs/README.md) for the architecture overview, key
77
+ abstractions, and architectural decisions. From there:
217
78
 
218
- ```ts
219
- import { defineServerPlugin } from "@hachej/boring-workspace/server"
220
-
221
- export const statusServerPlugin = defineServerPlugin({
222
- id: "build-status",
223
- agentTools: [buildTool], // Agent tools
224
- routes: async (app) => { // Fastify plugin function
225
- app.register(statusRoutes, { prefix: "/build-status" })
226
- },
227
- provisioning: {
228
- templateDirs: [
229
- { id: "build-status-seed", path: seedBuildDir, target: "." },
230
- ],
231
- },
232
- })
233
- ```
234
-
235
- ---
236
-
237
- ## Configuration
238
-
239
- ### WorkspaceProvider Props
240
-
241
- ```tsx
242
- <WorkspaceProvider
243
- chatPanel={ChatPanel} // Required: React component for chat
244
- workspaceId={id} // Required: workspace identifier
245
- plugins={[pluginA, pluginB]} // Optional: extend with custom plugins
246
- apiBaseUrl="http://localhost:3000" // Optional: backend URL
247
- authHeaders={...} // Optional: auth for HTTP requests
248
- layoutPreferences={...} // Optional: initial Dockview layout JSON
249
- />
250
- ```
251
-
252
- ---
253
-
254
- ## How @hachej/boring-workspace Compares
255
-
256
- | Feature | @hachej/boring-workspace | VS Code (Theia) | Custom layout |
257
- |---------|--------------------------|-----------------|---------------|
258
- | Panel layout | ✅ Dockview, drag/drop/split | ✅ Tabbed | ⚠️ Build yourself |
259
- | Plugin system | ✅ Panels + tabs + commands + catalogs | ✅ Extension API | ❌ DIY |
260
- | Agent bridge | ✅ Typed UiBridge pubsub | ❌ Not agent-native | ❌ DIY |
261
- | Code splitting | ✅ Auto-detects lazy factories | ⚠️ Require-based | ⚠️ Manual |
262
- | Setup time | ✅ ~10 lines | ❌ Heavy framework | ❌ Weeks |
263
-
264
- **When to use @hachej/boring-workspace:**
265
- - Building an agent-powered IDE with customizable panes
266
- - You need plugins to add panels without touching the shell
267
- - You want the agent to open things (files, data, charts) in the workbench
268
-
269
- **When it might not fit:**
270
- - You just want a chat box (use `@hachej/boring-agent` standalone)
271
- - You need a full VS Code replacement (no language server protocol support)
272
- - You want to control every pixel of the layout (use Dockview directly)
273
-
274
- ---
275
-
276
- ## Troubleshooting
277
-
278
- | Error | Cause | Fix |
279
- |-------|-------|-----|
280
- | `Panel not found: <id>` | Plugin not registered | Check `plugins` prop on `WorkspaceProvider` |
281
- | Blank panel / white screen | Lazy component threw | Check `PluginErrorBoundary` — inspect console |
282
- | `UiBridge not connected` | Backend not running | Verify `apiBaseUrl` and backend endpoint |
283
- | Commands not arriving | SSE blocked by proxy | Use `?poll=true` on `/api/v1/ui/commands/next` |
284
- | Plugin not loading | Missing `definePlugin` wrapper or `boring.front` manifest entry | Package front plugins must default-export `definePlugin({ ... })` and declare `boring.front` |
285
- | Duplicate panel IDs | Two plugins register same ID | Rename one panel's `id` field |
286
-
287
- ---
288
-
289
- ## Limitations
290
-
291
- - **Plugin panels share the same registry** — name collisions between plugins cause the last-registered panel to win. Namespace your IDs.
292
- - **No code editor language server** — The editor ships CodeMirror6 with syntax highlighting but no LSP. Semantic features (go-to-definition, rename) are not available.
293
- - **Rich text editor is TipTap-based** — It's included but opt-in via peer dependencies. Not all TipTap extensions are wired up.
294
- - **Frontend code must not value-import `@hachej/boring-agent`** — Package-neutral workspace code stays agent-free. Use the `chatPanel` injection pattern instead.
295
- - **Layout state persistence is the shell's job** — Workspace doesn't save/restore layouts between sessions. The shell owns `layoutPreferences`.
296
-
297
- ---
298
-
299
- ## FAQ
300
-
301
- **Q: What's the difference between a `panel` and a `left-tab`?**
302
- A: `panel` opens in the main Dockview area (center/right/bottom). `left-tab` is a persistent sidebar element that stays docked to the left. Users open panels programmatically; left-tabs are always visible.
303
-
304
- **Q: How do I lazy-load a panel?**
305
- A: Pass a zero-arg arrow function `() => import("./Pane").then(m => ({ default: m.Pane }))`. The registry auto-detects this pattern and wraps it in `React.lazy + Suspense`. Don't set `lazy: true`.
306
-
307
- **Q: Can the agent open my plugin's panel?**
308
- A: Yes. Register a `surface-resolver` that maps the agent's `SurfaceOpenRequest` to your panel ID. Then the agent can use `exec_ui` to open it.
309
-
310
- **Q: Why can't frontend workspace code import from `@hachej/boring-agent`?**
311
- A: The workspace package stays package-neutral so it can be used without the agent. The `chatPanel` prop injects the agent's UI, keeping the dependency inverted.
312
-
313
- **Q: What's `surface-resolver` for?**
314
- A: It decouples agent-side "open X" requests from frontend panel IDs. The agent says `{ kind: "open-series", seriesId: "GDPC1" }`, and the surface resolver maps that to `{ panelId: "series-chart", params: { seriesId: "GDPC1" } }`.
79
+ - [`docs/PLUGIN_SYSTEM.md`](./docs/PLUGIN_SYSTEM.md) — normative plugin/agent layer spec.
80
+ - [`docs/PLUGIN_STRUCTURE.md`](./docs/PLUGIN_STRUCTURE.md) plugin layout quick reference.
81
+ - [`docs/INTERFACES.md`](./docs/INTERFACES.md) — package boundaries and ownership rules.
315
82
 
316
83
  ---
317
84
 
318
85
  *About Contributions:* Please don't take this the wrong way, but I do not accept outside contributions for any of my projects. I simply don't have the mental bandwidth to review anything, and it's my name on the thing, so I'm responsible for any problems it causes; thus, the risk-reward is highly asymmetric from my perspective. I'd also have to worry about other "stakeholders," which seems unwise for tools I mostly make for myself for free. Feel free to submit issues, and even PRs if you want to illustrate a proposed fix, but know I won't merge them directly. Instead, I'll have Claude or Codex review submissions via `gh` and independently decide whether and how to address them. Bug reports in particular are welcome. Sorry if this offends, but I want to avoid wasted time and hurt feelings. I understand this isn't in sync with the prevailing open-source ethos that seeks community contributions, but it's the only way I can move at this velocity and keep my sanity.
319
86
 
320
- ---
321
-
322
87
  ## License
323
88
 
324
89
  MIT
@@ -2,7 +2,7 @@ import { jsx as c, jsxs as z } from "react/jsx-runtime";
2
2
  import { useRef as D, useEffect as A, useMemo as M, useCallback as w, createContext as S, useContext as $ } from "react";
3
3
  import { Tree as E } from "react-arborist";
4
4
  import { FolderOpenIcon as J, FolderIcon as K, ChevronRightIcon as U, Loader2Icon as V } from "lucide-react";
5
- import { L as X } from "./WorkspaceProvider-uuxyAx3i.js";
5
+ import { L as X } from "./WorkspaceProvider-DkZAxsYo.js";
6
6
  import { EmptyState as Y, Input as Z } from "@hachej/boring-ui-kit";
7
7
  import { c as N } from "./utils-B6yFEsav.js";
8
8
  import { createDragDropManager as y } from "dnd-core";