@hachej/boring-workspace 0.1.13 → 0.1.16

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 (34) hide show
  1. package/README.md +270 -42
  2. package/dist/CommandPalette-NOEOVkN2.js +5714 -0
  3. package/dist/{FileTree-BVfqs3rR.js → FileTree-Dl-qUAB0.js} +9 -9
  4. package/dist/MarkdownEditor-yc6mFsnI.js +533 -0
  5. package/dist/{WorkspaceLoadingState-BjZGQLS_.js → WorkspaceLoadingState-CSZfENWe.js} +145 -124
  6. package/dist/agent-tool-DEtfQPVB.d.ts +100 -0
  7. package/dist/app-front.d.ts +79 -67
  8. package/dist/app-front.js +253 -241
  9. package/dist/app-server.d.ts +17 -12
  10. package/dist/app-server.js +80 -10
  11. package/dist/{bootstrapServer-BRUqUpVW.d.ts → bootstrapServer-BreQ9QBc.d.ts} +8 -2
  12. package/dist/server.d.ts +10 -32
  13. package/dist/server.js +22 -127
  14. package/dist/shared.d.ts +1 -2
  15. package/dist/testing.d.ts +0 -63
  16. package/dist/testing.js +2248 -2401
  17. package/dist/workspace.css +1616 -974
  18. package/dist/workspace.d.ts +111 -450
  19. package/dist/workspace.js +417 -1635
  20. package/docs/INTERFACES.md +2 -2
  21. package/docs/PLUGIN_STRUCTURE.md +1 -1
  22. package/docs/plans/ASK_USER_QUESTIONS_PLUGIN_SPEC.md +131 -263
  23. package/docs/plans/GENERIC_EXPLORER_PLUGIN_PLAN.md +29 -27
  24. package/docs/plans/MACRO_PLUGIN_GENERIC_HELPERS_AUDIT.md +12 -12
  25. package/docs/plans/PANE_TO_AGENT_CHAT_ACTIONS_SPEC.md +366 -0
  26. package/docs/plans/README.md +2 -0
  27. package/docs/plans/archive/PLUGIN_MODEL.md +14 -14
  28. package/docs/plans/archive/SRC_FOLDER_REORG_PLAN.md +2 -3
  29. package/docs/plans/archive/WORKSPACE_V2_PLAN.md +1 -1
  30. package/package.json +3 -6
  31. package/dist/CommandPalette-Dme9em28.js +0 -5506
  32. package/dist/MarkdownEditor-CcCDF65H.js +0 -502
  33. package/dist/agent-tool-NvxKfist.d.ts +0 -28
  34. package/dist/explorer-DtLUnuah.d.ts +0 -129
@@ -35,8 +35,8 @@ commands, and default workspace plugins.
35
35
  - Filesystem data: `src/plugins/filesystemPlugin/front/data`
36
36
  - Filesystem client, hooks, event stream, and cache invalidation are plugin
37
37
  owned.
38
- - Data catalog: `src/plugins/dataCatalogPlugin/front` and
39
- `src/plugins/dataCatalogPlugin/server`
38
+ - Data catalog package: `@hachej/boring-data-catalog/front` and
39
+ `@hachej/boring-data-catalog/server`
40
40
  - Catalog rows are opened through `openSurface`; row-to-panel mapping belongs
41
41
  to the plugin resolver.
42
42
  - Server plugins: `src/server/plugins`
@@ -133,7 +133,7 @@ slash commands.
133
133
  ## Current Plugins
134
134
 
135
135
  - `packages/workspace/src/plugins/filesystemPlugin`
136
- - `packages/workspace/src/plugins/dataCatalogPlugin`
136
+ - Data catalog package: `@hachej/boring-data-catalog` (`plugins/data-catalog/src`)
137
137
  - `apps/workspace-playground/src/plugins/playgroundDataCatalog`
138
138
  - Macro plugin example: `hachej/boring-macro` (`src/plugins/macro`)
139
139
 
@@ -1,322 +1,190 @@
1
1
  # Ask User Questions Plugin Spec
2
2
 
3
- Last updated: 2026-05-08
4
-
5
- ## Summary
6
-
7
- Add a blocking `ask_user` capability for agent sessions that opens a Workspace
8
- page named **Questions**, renders an agent-generated form, lets the user submit
9
- answers, and returns those answers to the waiting agent tool call.
10
-
11
- The Workspace implementation owns the page/plugin UI, form primitives, and UI
12
- bridge command handling. The Agent implementation owns the blocking tool runtime
13
- and session-level waiter. The browser must submit answers through the Workspace
14
- UI bridge command path, not through ad-hoc form POST semantics.
15
-
16
- ## Goals
17
-
18
- - Provide an agent tool that asks the user for structured input and blocks until
19
- the user answers or cancels.
20
- - Open or focus a Workspace page/panel named `Questions` whenever a question is
21
- pending.
22
- - Render forms from a portable, JSON-serializable schema generated by the agent.
23
- - Provide primitive React UI components and hooks so app shells can render these
24
- forms in a standard way.
25
- - Persist the single pending question per agent session across browser reloads.
26
- - Send submitted answers back to the waiting agent through `UiBridge.postCommand`
27
- as the single user-dispatch source.
28
- - Keep Workspace base front/shared code package-neutral; no value imports from
29
- `@boring/agent` outside allowed app composition boundaries.
3
+ Status: implemented in `plugins/askUserPlugin`.
30
4
 
31
- ## Non-goals
32
-
33
- - Multiple simultaneous pending questions per agent session.
34
- - File upload fields in the first version.
35
- - Full process-crash restoration of an already-blocked in-memory tool call,
36
- unless the current agent session runtime already supports rehydrating waiters.
37
- - Using chat-stream `data-ui-command` parts as the source of truth for answers.
38
- - Making `pi-ask-user` the Workspace UX implementation dependency.
39
-
40
- ## User flow
41
-
42
- 1. Agent calls `ask_user` with title, optional context, and form schema.
43
- 2. Server creates a pending question record for the current agent session.
44
- 3. Agent tool blocks on an `AskUserCoordinator` waiter.
45
- 4. Workspace receives/loads the pending question and opens or focuses the
46
- `Questions` page.
47
- 5. User fills the form and clicks Submit.
48
- 6. Questions page calls `uiBridge.postCommand({ type: "questions.submit", ... })`.
49
- 7. Workspace bridge server handler validates the command and records the answer.
50
- 8. `AskUserCoordinator` resolves the waiter.
51
- 9. Tool returns the structured answer to the agent and the agent continues.
52
-
53
- Cancel follows the same path with `type: "questions.cancel"` and returns a
54
- stable cancelled result to the tool caller.
5
+ ## Goal
55
6
 
56
- ## Package placement
7
+ Provide a Workspace-owned `ask_user` agent tool that asks the user a blocking,
8
+ structured question in a **Questions** workbench pane and returns the browser
9
+ answer to the waiting agent tool call.
57
10
 
58
- ### `@boring/workspace/shared`
11
+ The ask-user domain stays inside the plugin:
59
12
 
60
- Define browser-safe contracts only:
13
+ - shared constants, types, schemas, and error codes
14
+ - server runtime, store, routes, state publisher, and tool wrapper
15
+ - front provider, Questions pane, client, form primitives, and surface resolver
61
16
 
62
- - `AskUserQuestion`
63
- - `AskUserFormSchema`
64
- - `AskUserField`
65
- - `AskUserAnswer`
66
- - `QuestionsSubmitCommand`
67
- - `QuestionsCancelCommand`
68
- - stable error codes imported from the canonical error-code enum
17
+ Generic Workspace and Agent layers know only generic concepts:
69
18
 
70
- No `node:*`, no `Buffer`, no `@boring/agent` value imports.
19
+ - Workspace attention blockers
20
+ - generic blocker labels/actions
21
+ - generic `openSurface` dispatch from Workspace chrome
22
+ - generic tool execution context with the active agent `sessionId`
71
23
 
72
- ### `@boring/workspace/plugins/questionsPlugin`
73
-
74
- New default Workspace plugin that owns:
75
-
76
- - `front/QuestionsPage.tsx`
77
- - `front/QuestionsPanel.tsx`
78
- - `front/usePendingQuestion.ts`
79
- - `front/primitives/QuestionForm.tsx`
80
- - `front/primitives/QuestionField.tsx`
81
- - field primitive components
82
- - `server/questionsBridge.ts`
83
- - `shared/types.ts`
84
-
85
- Plugin outputs should include at minimum:
24
+ ## Non-goals
86
25
 
87
- - a panel/page output for `Questions`
88
- - a command output or bridge binding for `questions.submit`
89
- - a command output or bridge binding for `questions.cancel`
90
- - a surface resolver if the workspace opens the page through `openSurface`
26
+ - No progressive/draft form streaming in this PR.
27
+ - No JSON-Schema `properties` compatibility layer.
28
+ - No heuristic A/B fallback when schema is omitted.
29
+ - No chat-inline form UI; chat only shows generic blocker/status affordances.
30
+ - No ask-user-specific strings or events in `@hachej/boring-agent`.
91
31
 
92
- ### `@boring/workspace/app/*`
32
+ Agents must provide the final form schema up front.
93
33
 
94
- App composition may wire the questions plugin to `@boring/agent/server` tool
95
- registration APIs. This is the allowed boundary for Workspace + Agent product
96
- composition.
34
+ ## Tool contract
97
35
 
98
- ### `@boring/agent/server`
36
+ Tool name: `ask_user`
99
37
 
100
- Agent package should expose the blocking `ask_user` tool factory and an adapter
101
- interface that Workspace can satisfy:
38
+ Input:
102
39
 
103
40
  ```ts
104
- interface AskUserRuntime {
105
- askUser(request: AskUserRequest): Promise<AskUserToolResult>
41
+ type AskUserToolInput = {
42
+ title: string
43
+ context?: string
44
+ schema: AskUserFormSchema
45
+ timeoutMs?: number
106
46
  }
107
- ```
108
-
109
- Workspace app/server composition injects a runtime that stores the pending
110
- question and resolves through UI bridge answers.
111
-
112
- ## Form schema
113
47
 
114
- Use a small JSON-schema-lite contract. It is serializable, easy to render in
115
- React, and avoids forcing Zod or another runtime validator across the shared
116
- browser/server boundary. A later helper may compile from Zod or JSON Schema into
117
- this format.
118
-
119
- ```ts
120
48
  type AskUserFormSchema = {
49
+ wireVersion: 1
121
50
  fields: AskUserField[]
122
51
  submitLabel?: string
123
52
  }
124
-
125
- type AskUserField =
126
- | {
127
- type: "text"
128
- name: string
129
- label: string
130
- required?: boolean
131
- placeholder?: string
132
- defaultValue?: string
133
- helpText?: string
134
- }
135
- | {
136
- type: "textarea"
137
- name: string
138
- label: string
139
- required?: boolean
140
- placeholder?: string
141
- defaultValue?: string
142
- helpText?: string
143
- }
144
- | {
145
- type: "select"
146
- name: string
147
- label: string
148
- required?: boolean
149
- options: AskUserOption[]
150
- defaultValue?: string
151
- helpText?: string
152
- }
153
- | {
154
- type: "multiselect"
155
- name: string
156
- label: string
157
- required?: boolean
158
- options: AskUserOption[]
159
- defaultValue?: string[]
160
- helpText?: string
161
- }
162
- | {
163
- type: "checkbox"
164
- name: string
165
- label: string
166
- defaultValue?: boolean
167
- helpText?: string
168
- }
169
- | {
170
- type: "radio"
171
- name: string
172
- label: string
173
- required?: boolean
174
- options: AskUserOption[]
175
- defaultValue?: string
176
- helpText?: string
177
- }
178
-
179
- type AskUserOption = {
180
- value: string
181
- label: string
182
- description?: string
183
- }
184
53
  ```
185
54
 
186
- Answer shape:
55
+ Supported field types:
187
56
 
188
- ```ts
189
- type AskUserAnswer = {
190
- questionId: string
191
- sessionId: string
192
- values: Record<string, string | string[] | boolean | null>
193
- submittedAt: string
194
- }
195
- ```
57
+ - `text`
58
+ - `textarea`
59
+ - `select`
60
+ - `radio`
61
+ - `multiselect`
62
+ - `checkbox`
63
+ - `number`
64
+
65
+ The tool prompt explicitly instructs the model to emit this schema shape and not
66
+ JSON Schema `properties`.
196
67
 
197
- Tool result shape:
68
+ Output:
198
69
 
199
70
  ```ts
200
71
  type AskUserToolResult =
201
72
  | { status: "answered"; answer: AskUserAnswer }
202
- | { status: "cancelled"; questionId: string; sessionId: string }
73
+ | { status: "cancelled"; questionId: string; sessionId: string; reason: AskUserCancelReason }
203
74
  ```
204
75
 
205
- ## Persistence
76
+ Tool errors are returned as normal tool errors when input validation fails,
77
+ runtime limits reject the request, timeout/abort occurs, or the user cancels.
206
78
 
207
- Only one pending question is allowed per agent session. Creating a new pending
208
- question for a session that already has one should fail with a stable error code
209
- or replace only if an explicit future `replacePending` option is added.
79
+ ## Runtime flow
210
80
 
211
- Store interface:
81
+ 1. Agent calls `ask_user` with complete schema.
82
+ 2. Tool execution context provides active chat/session id when available.
83
+ 3. `AskUserRuntime.ask()` validates schema and creates one ready pending question.
84
+ 4. Store persists the question and transcript events.
85
+ 5. State publisher writes `questions.pending` into Workspace UI state.
86
+ 6. Runtime best-effort posts `openSurface { kind: "questions" }`.
87
+ 7. Questions pane renders the form from command metadata or persisted UI state.
88
+ 8. User submits/cancels through `/api/v1/questions/commands`.
89
+ 9. Server validates session/principal/token/schema and resolves the in-process waiter.
90
+ 10. Agent receives answered/cancelled result.
212
91
 
213
- ```ts
214
- interface AskUserStore {
215
- getPending(sessionId: string): Promise<AskUserQuestion | null>
216
- createPending(question: AskUserQuestion): Promise<void>
217
- answer(questionId: string, answer: AskUserAnswer): Promise<void>
218
- cancel(questionId: string): Promise<void>
219
- clearPending(sessionId: string): Promise<void>
220
- }
221
- ```
92
+ Missed UI open acks or failed `openSurface` dispatch do not cancel the pending
93
+ question. The persisted state lets browser refresh/reconnect recover.
222
94
 
223
- Initial implementation should provide a file-backed or existing workspace-store
224
- adapter. Core/cloud can later inject a DB-backed implementation.
95
+ ## Store contract
225
96
 
226
- Persistence requirement for v1: pending question survives browser reload and the
227
- Questions page can re-render it. Full server-process crash resumption of the
228
- blocked tool waiter is a later concern unless already provided by the current
229
- agent session runtime.
97
+ `AskUserStore` owns persisted pending questions, answers, and transcript events.
98
+ The file-backed default store is suitable for standalone/dev use and can be
99
+ replaced by app/core/cloud storage later.
230
100
 
231
- ## UI bridge commands
101
+ Important invariants:
232
102
 
233
- The browser submits through `UiBridge.postCommand`:
103
+ - one pending ready question per session
104
+ - terminal states are guarded (`answered`, `cancelled`, `abandoned`)
105
+ - writes are serialized
106
+ - listener failures do not roll back mutations
107
+ - transcript events never contain browser-supplied schema
234
108
 
235
- ```ts
236
- uiBridge.postCommand({
237
- type: "questions.submit",
238
- questionId,
239
- sessionId,
240
- values,
241
- })
242
- ```
109
+ ## Browser command security
243
110
 
244
- Cancel:
111
+ Browser commands include the question id, session id, and answer token. The
112
+ bridge validates:
245
113
 
246
- ```ts
247
- uiBridge.postCommand({
248
- type: "questions.cancel",
249
- questionId,
250
- sessionId,
251
- })
252
- ```
114
+ - command payload shape
115
+ - stored question exists
116
+ - session id matches
117
+ - auth context/principal matches when configured
118
+ - answer token matches using constant-time comparison
119
+ - answer values match the server-stored schema
253
120
 
254
- Server bridge handlers must:
121
+ Duplicate submit after answered is idempotent. Submit after cancel is rejected.
122
+ If the runtime waiter is gone, submit abandons and returns conflict instead of
123
+ falsely reporting answered.
255
124
 
256
- - validate `sessionId` and `questionId`
257
- - validate submitted values against the form schema
258
- - persist answer/cancel state
259
- - resolve the matching `AskUserCoordinator` waiter
260
- - emit/update Workspace state so the Questions page leaves pending mode
125
+ ## UI state and recovery
261
126
 
262
- No direct browser form route should be treated as the conceptual API. If an HTTP
263
- route exists underneath, it is only transport for the UI bridge.
127
+ The plugin owns `ASK_USER_UI_STATE_SLOTS.PENDING` (`questions.pending`). Server
128
+ plugins declare preserved UI state keys so generic Workspace routes do not import
129
+ ask-user constants.
264
130
 
265
- ## Questions page behavior
131
+ Pending state shape:
266
132
 
267
- - Register/open a page or panel titled `Questions`.
268
- - Auto-open or focus when a new pending question appears.
269
- - Show title, context, form fields, submit button, and cancel affordance.
270
- - Disable submit while validation fails or command is in flight.
271
- - After submit, show a short answered state and return focus behavior to the
272
- workspace shell if appropriate.
273
- - If no pending question exists, show an empty state: "No questions right now."
133
+ ```ts
134
+ type AskUserPendingState = {
135
+ question: AskUserQuestion | null
136
+ }
137
+ ```
138
+
139
+ The front provider refreshes pending state on mount, window focus, visibility
140
+ restore, and UI-command events. It does not poll continuously.
141
+
142
+ ## Composer blocking
274
143
 
275
- ## Primitive UI package
144
+ Pending ready questions add a `WorkspaceAttentionBlocker` with generic actions:
145
+
146
+ ```ts
147
+ {
148
+ reason: "waiting_for_user_input",
149
+ label: "Answer the question in Questions to continue",
150
+ actions: [{ id: "open", label: "Open Questions" }]
151
+ }
152
+ ```
276
153
 
277
- Expose primitives from Workspace front code so apps can customize the page while
278
- keeping standard schema behavior:
154
+ `@hachej/boring-agent` receives only generic blocker shape/actions. Workspace
155
+ chrome handles action ids against its richer workspace blocker metadata.
279
156
 
280
- - `<QuestionForm schema values onValuesChange onSubmit onCancel />`
281
- - `<QuestionField field value onChange error />`
282
- - `<QuestionSubmitButton />`
283
- - `<QuestionCancelButton />`
284
- - `useQuestionForm(schema, initialValues?)`
285
- - `validateQuestionValues(schema, values)`
157
+ Policy:
286
158
 
287
- Primitives should be headless-enough to style, but the default Questions page
288
- should provide a polished complete UI.
159
+ - normal busy chat blocks send unless native follow-up is supported
160
+ - pending ask-user always blocks composer, even with native follow-up support
161
+ - Stop remains clickable while blocked
162
+ - Stop cancels the pending question for that session and closes Questions pane
289
163
 
290
- ## `pi-ask-user` evaluation
164
+ ## App wiring
291
165
 
292
- `pi-ask-user` provides a native Pi `ask_user` tool with TUI overlay/inline UI,
293
- selection lists, freeform input, multi-select, timeout, and a useful bundled
294
- skill. It is good prior art and may be useful as a standalone CLI fallback.
166
+ The ask-user plugin is not hard-wired into `createWorkspaceAgentServer()` or
167
+ `WorkspaceAgentFront()`. Host apps install both halves explicitly:
295
168
 
296
- Do not make it the primary Workspace implementation because it does not provide:
169
+ - front: pass `askUserPlugin` through the workspace `plugins` prop
170
+ - server: pass a plugin factory that creates `createAskUserPluginBundle({ workspaceRoot, bridge })`
297
171
 
298
- - Workspace page/panel integration
299
- - generated rich React form rendering
300
- - Workspace persistence semantics
301
- - answer dispatch through `UiBridge.postCommand`
302
- - app-shell primitive components
172
+ Ask-user server plugin contributes:
303
173
 
304
- Recommended stance:
174
+ - `ask_user` agent tool
175
+ - Questions routes
176
+ - system prompt snippet
177
+ - preserved UI state key
305
178
 
306
- - Use `pi-ask-user` as reference for prompt/tool ergonomics.
307
- - Consider optional CLI/TUI fallback for standalone `@boring/agent` mode.
308
- - Keep Workspace Questions plugin implementation first-party.
179
+ The state publisher is started only when the host app installs the server plugin
180
+ and is disposed on Fastify close.
309
181
 
310
- ## Acceptance criteria for implementation bead
182
+ ## Tests to keep
311
183
 
312
- - Shared schema/types exist and are browser-safe.
313
- - Questions plugin registers a `Questions` page/panel.
314
- - Agent/session can create one pending question and block for the answer.
315
- - Pending question persists across browser reload.
316
- - Default page renders text, textarea, select, multiselect, checkbox, and radio.
317
- - Submit uses `UiBridge.postCommand`, not direct conceptual route dispatch.
318
- - Submitted values resolve the blocking tool call.
319
- - Cancel resolves the tool call with `status: "cancelled"`.
320
- - Primitive form components/hooks are exported for app-shell reuse.
321
- - Unit tests cover schema validation and one-pending-question enforcement.
322
- - Integration test or harness test covers ask -> render -> submit -> tool result.
184
+ - schema validation limits and command payloads
185
+ - store persistence and terminal guards
186
+ - runtime answer/cancel/timeout/abort/orphan behavior
187
+ - bridge auth/session/token/answer validation
188
+ - front Questions pane submit/cancel/stop behavior
189
+ - generic ChatPanelHost blocker action behavior
190
+ - host app front/server plugin installation symmetry
@@ -7,9 +7,9 @@ Status: draft plan. No code has moved yet.
7
7
  ## Problem
8
8
 
9
9
  Workspace currently has a good generic `DataExplorer` primitive under
10
- `src/front/components/DataExplorer`. The data-catalog-specific legacy wrappers
10
+ `@hachej/boring-data-explorer/front`. The data-catalog-specific legacy wrappers
11
11
  under `src/front/components/data-catalog` have been removed; data catalog behavior
12
- now lives only in `dataCatalogPlugin`. Feret v2 shows the next requirement: not
12
+ now lives only in the `@hachej/boring-data-catalog` package. Feret v2 shows the next requirement: not
13
13
  just flat/faceted database rows, but mixed project trees with
14
14
  filesystem rows, virtual DB rows, section filters, friendly labels, and domain
15
15
  open routing.
@@ -28,7 +28,7 @@ We need a cleaner ownership model:
28
28
  Create a dedicated generic explorer plugin/family:
29
29
 
30
30
  ```txt
31
- packages/workspace/src/plugins/explorerPlugin/
31
+ plugins/data-explorer/src/
32
32
  index.tsx
33
33
  constants.ts
34
34
  types.ts
@@ -47,13 +47,13 @@ emit panes/outputs and own a feature contract, but they do not encode an app
47
47
  domain like files, data catalog, or Feret. Domain plugins compose it:
48
48
 
49
49
  ```txt
50
- dataCatalogPlugin -> uses explorerPlugin for generic explorer rendering
51
- filesystemPlugin -> may use explorerPlugin for project/friendly trees later
52
- Feret app plugin -> uses explorerPlugin directly for Project Tree and Data/Feret
53
- future domain plugin -> uses explorerPlugin for symbols/docs/jobs/issues/etc.
50
+ data catalog package -> uses data explorer package for generic explorer rendering
51
+ filesystemPlugin -> may use data explorer package for project/friendly trees later
52
+ Feret app plugin -> uses data explorer package directly for Project Tree and Data/Feret
53
+ future domain plugin -> uses data explorer package for symbols/docs/jobs/issues/etc.
54
54
  ```
55
55
 
56
- Do **not** put generic explorer under `dataCatalogPlugin`. That would make other
56
+ Do **not** put generic explorer under the `@hachej/boring-data-catalog` package. That would make other
57
57
  domains depend on a data-catalog name and blur ownership.
58
58
 
59
59
  ## Target Package Shape
@@ -83,9 +83,11 @@ src/front/
83
83
  toast/
84
84
 
85
85
  src/plugins/
86
- explorerPlugin/ # generic explorer feature family
87
- dataCatalogPlugin/ # data catalog specialization
88
86
  filesystemPlugin/ # files/editors/tree specialization
87
+
88
+ root plugins/
89
+ data-explorer/ # @hachej/boring-data-explorer
90
+ data-catalog/ # @hachej/boring-data-catalog
89
91
  ```
90
92
 
91
93
  ## Explorer Plugin API Shape
@@ -282,10 +284,10 @@ Implementation must validate adapter/mode combinations at runtime too:
282
284
 
283
285
  ## Data Catalog After Refactor
284
286
 
285
- `dataCatalogPlugin` becomes a specialization that composes explorer outputs:
287
+ the `@hachej/boring-data-catalog` package becomes a specialization that composes explorer outputs:
286
288
 
287
289
  ```txt
288
- dataCatalogPlugin/
290
+ @hachej/boring-data-catalog/
289
291
  index.tsx # createDataCatalogPlugin, appendDataCatalogOutputs
290
292
  constants.ts
291
293
  types.ts
@@ -312,7 +314,7 @@ Non-responsibilities:
312
314
 
313
315
  Legacy `src/front/components/data-catalog` is removed. Consumers should use
314
316
  `createDataCatalogPlugin()` / `appendDataCatalogOutputs()` or compose
315
- `DataExplorer` directly until it moves to `explorerPlugin`.
317
+ `DataExplorer` directly until it moves to the `@hachej/boring-data-explorer` package.
316
318
 
317
319
  ## Feret v2 Requirements Driving Explorer Plugin
318
320
 
@@ -356,7 +358,7 @@ filters.
356
358
 
357
359
  | Current location | Decision | Rationale |
358
360
  | --------------------------------------------- | ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
359
- | `front/components/DataExplorer/*` | Move to `plugins/explorerPlugin/*` | Generic feature family, but pane-capable and adapter-owned; multiple plugins depend on it. |
361
+ | `@hachej/boring-data-explorer/front` | Extracted to `plugins/data-explorer/src/front/*` | Generic feature family, but pane-capable and adapter-owned; multiple plugins depend on it. |
360
362
  | `front/components/data-catalog/*` | Removed | Data-catalog naming is domain-ish; real data catalog behavior is plugin-owned already. |
361
363
  | `front/components/CommandPalette.tsx` | Keep in `front/components` | Generic workspace chrome over command/catalog registries, not a domain plugin. |
362
364
  | `front/components/recent/*` | Keep in `front/components` for now | Generic command/catalog recent state used by CommandPalette. Could become `commandPalette` subfolder later. |
@@ -374,39 +376,39 @@ filters.
374
376
  | `plugins/filesystemPlugin/front/code-editor/*` | Keep in filesystem plugin | File editor domain and file data hooks. |
375
377
  | `plugins/filesystemPlugin/front/markdown-editor/*` | Keep in filesystem plugin | File editor domain and file data hooks. |
376
378
  | `plugins/filesystemPlugin/front/empty-file-panel/*` | Keep in filesystem plugin | File-specific empty panel. |
377
- | `plugins/dataCatalogPlugin/front/*` | Keep plugin-owned, but import explorer from `explorerPlugin` | Domain specialization. |
379
+ | `@hachej/boring-data-catalog/front` | Extracted to `plugins/data-catalog/src/front/*`; imports explorer from `@hachej/boring-data-explorer` | Domain specialization. |
378
380
 
379
381
  ## Public API Migration
380
382
 
381
383
  Avoid breaking current consumers in the first pass.
382
384
 
383
- 1. Add `plugins/explorerPlugin` and re-export explorer APIs from old package
385
+ 1. Add `plugins/data-explorer` and re-export explorer APIs from old package
384
386
  root names:
385
387
  - `DataExplorer` stays exported, but source moves.
386
388
  - `ExplorerRow`, `ExplorerAdapter`, `FacetConfig`, etc. stay exported with
387
389
  compatibility aliases.
388
- 2. Keep `front/components/DataExplorer/index.ts` as a thin deprecated re-export
389
- for one release if internal import churn is high.
390
+ 2. Keep any workspace compatibility re-export thin and temporary if internal
391
+ import churn requires it; the canonical API is `@hachej/boring-data-explorer/front`.
390
392
  3. `DataCatalog` and `DataCatalogPane` root exports are removed now; use data catalog plugin helpers instead.
391
- 4. Move app/plugin imports to `plugins/explorerPlugin` or package-root exports.
393
+ 4. Move app/plugin imports to `plugins/data-explorer` or package-root exports.
392
394
 
393
395
  ## Implementation Beads
394
396
 
395
397
  ### Bead 1 — Explorer plugin skeleton
396
398
 
397
- - Add `src/plugins/explorerPlugin` with current `DataExplorer` code moved mostly
399
+ - Add `plugins/data-explorer/src` with current `DataExplorer` code moved mostly
398
400
  intact.
399
401
  - Export compatibility aliases.
400
- - Update internal imports from `front/components/DataExplorer` to
401
- `plugins/explorerPlugin`.
402
+ - Update internal imports from `@hachej/boring-data-explorer/front` to
403
+ `plugins/data-explorer`.
402
404
  - Tests: current DataExplorer tests pass unchanged after path update.
403
405
 
404
406
  ### Bead 2 — Data catalog plugin composition
405
407
 
406
- - Change `dataCatalogPlugin` to import explorer APIs from `explorerPlugin`.
408
+ - Change the `@hachej/boring-data-catalog` package to import explorer APIs from `@hachej/boring-data-explorer`.
407
409
  - Split data catalog helpers into `catalogs.ts` if useful.
408
410
  - Keep `front/components/data-catalog` removed; do not add compatibility wrappers back.
409
- - Tests: dataCatalogPlugin tests and public API tests.
411
+ - Tests: data catalog package tests and public API tests.
410
412
 
411
413
  ### Bead 3 — Tree adapter design
412
414
 
@@ -441,9 +443,9 @@ filesystem tree to explorer tree` before the legacy data-catalog removal bead.
441
443
  - Do not force filesystem to depend on data catalog.
442
444
  - Do not rewrite file tree in the first bead.
443
445
  - Do not remove public exports without an explicit breaking cleanup bead.
444
- - Do not allow reverse imports from `explorerPlugin` into domain plugins. Add or
445
- extend invariant lint so allowed direction is domain plugin -> explorerPlugin,
446
- never explorerPlugin -> dataCatalogPlugin/filesystemPlugin/app plugins.
446
+ - Do not allow reverse imports from `@hachej/boring-data-explorer` into domain plugins. Add or
447
+ extend invariant lint so allowed direction is domain plugin -> data explorer package,
448
+ never data explorer package -> data catalog package, filesystemPlugin, or app plugins.
447
449
 
448
450
  ## Acceptance Criteria
449
451