@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.
- package/LICENSE +21 -0
- package/README.md +94 -0
- package/dist/CodeEditor-DQqOn4xz.js +266 -0
- package/dist/CommandPalette-aM61U-b0.js +5229 -0
- package/dist/FileTree-DRq_bfue.js +245 -0
- package/dist/MarkdownEditor-DjiHxnRv.js +349 -0
- package/dist/WorkspaceLoadingState-By0dZoPD.js +568 -0
- package/dist/agent-tool-NvxKfist.d.ts +28 -0
- package/dist/app-front.d.ts +485 -0
- package/dist/app-front.js +452 -0
- package/dist/app-server.d.ts +53 -0
- package/dist/app-server.js +769 -0
- package/dist/bootstrapServer-BRUqUpVW.d.ts +66 -0
- package/dist/boring-workspace.css +1 -0
- package/dist/charts.d.ts +114 -0
- package/dist/charts.js +143 -0
- package/dist/events.d.ts +178 -0
- package/dist/events.js +88 -0
- package/dist/explorer-DtLUnuah.d.ts +129 -0
- package/dist/panel-DnvDNQac.js +6 -0
- package/dist/server.d.ts +84 -0
- package/dist/server.js +811 -0
- package/dist/shared.d.ts +113 -0
- package/dist/shared.js +11 -0
- package/dist/testing-e2e.d.ts +68 -0
- package/dist/testing-e2e.js +45 -0
- package/dist/testing.d.ts +464 -0
- package/dist/testing.js +10984 -0
- package/dist/utils-B6yFEsav.js +8 -0
- package/dist/workspace.css +5780 -0
- package/dist/workspace.d.ts +2119 -0
- package/dist/workspace.js +1884 -0
- package/docs/INTERFACES.md +58 -0
- package/docs/PLUGIN_STRUCTURE.md +162 -0
- package/docs/README.md +19 -0
- package/docs/bridge.md +135 -0
- package/docs/panels.md +102 -0
- package/docs/plans/GENERIC_EXPLORER_PLUGIN_PLAN.md +455 -0
- package/docs/plans/MACRO_PLUGIN_GENERIC_HELPERS_AUDIT.md +962 -0
- package/docs/plans/PLUGIN_OUTPUTS_ISOLATION_PLAN.md +301 -0
- package/docs/plans/README.md +9 -0
- package/docs/plans/UI_BRIDGE_OWNERSHIP_REFACTOR.md +303 -0
- package/docs/plans/archive/CODE_OWNERSHIP_CLEANUP_PLAN.md +387 -0
- package/docs/plans/archive/COMMAND_PALETTE_REGISTRY.md +814 -0
- package/docs/plans/archive/DECLARATIVE_LAYOUT_MIGRATION.md +277 -0
- package/docs/plans/archive/PLUGIN_MODEL.md +3674 -0
- package/docs/plans/archive/SRC_FOLDER_REORG_PLAN.md +307 -0
- package/docs/plans/archive/UNIFIED_EVENT_BUS.md +647 -0
- package/docs/plans/archive/WORKSPACE_V2_PLAN.md +2489 -0
- package/docs/plugins.md +158 -0
- package/package.json +164 -0
|
@@ -0,0 +1,962 @@
|
|
|
1
|
+
# Macro Plugin Generic Helpers Audit
|
|
2
|
+
|
|
3
|
+
Last updated: 2026-05-01
|
|
4
|
+
|
|
5
|
+
Status: draft audit / extraction plan. No implementation yet.
|
|
6
|
+
|
|
7
|
+
## Goal
|
|
8
|
+
|
|
9
|
+
Inspect `apps/boring-macro-v2/src/plugins/macro` in detail and identify which
|
|
10
|
+
pieces are truly macro-domain logic versus reusable workspace/plugin mechanics.
|
|
11
|
+
The aim is to reduce boilerplate in macro and future apps without moving macro
|
|
12
|
+
meaning into `@boring/workspace`.
|
|
13
|
+
|
|
14
|
+
This plan complements `GENERIC_EXPLORER_PLUGIN_PLAN.md`: macro is a concrete
|
|
15
|
+
consumer that should pressure-test generic explorer, surface resolver, open
|
|
16
|
+
surface, event, and REST adapter helpers.
|
|
17
|
+
|
|
18
|
+
## Current Macro Plugin Shape
|
|
19
|
+
|
|
20
|
+
```txt
|
|
21
|
+
apps/boring-macro-v2/src/plugins/macro/
|
|
22
|
+
index.tsx # client plugin composition + chat suggestions
|
|
23
|
+
constants.ts # macro ids, panel ids, surface kinds
|
|
24
|
+
panels.tsx # definePanel wrappers for chart/deck
|
|
25
|
+
surfaceResolver.ts # macro surface routing rules
|
|
26
|
+
catalogs.ts # data catalog output options
|
|
27
|
+
data/
|
|
28
|
+
macroSeriesAdapter.ts # REST -> ExplorerAdapter
|
|
29
|
+
macroSeriesData.ts # series fetch/cache for panes
|
|
30
|
+
macroSeriesTypes.ts # series payload types
|
|
31
|
+
macroSeriesUi.ts # frequency labels, colors, openSeriesPane
|
|
32
|
+
panels/
|
|
33
|
+
ChartCanvasPane.tsx # chart UI
|
|
34
|
+
DeckPane.tsx # markdown deck UI + embeds
|
|
35
|
+
routes/
|
|
36
|
+
StandaloneDeckRoute.tsx # app route component
|
|
37
|
+
server/
|
|
38
|
+
index.ts # server plugin, prompt, provisioning, routes
|
|
39
|
+
routes/macro.ts # Fastify API routes
|
|
40
|
+
services/* # ClickHouse/FRED domain services
|
|
41
|
+
tools/macroTools.ts # agent tools
|
|
42
|
+
sdk/, transforms/, workspace-template/
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Ownership Rule
|
|
46
|
+
|
|
47
|
+
Move generic mechanics only when at least two plugins/apps can use them.
|
|
48
|
+
Keep macro semantics in macro.
|
|
49
|
+
|
|
50
|
+
Macro keeps:
|
|
51
|
+
|
|
52
|
+
- economic series concepts
|
|
53
|
+
- FRED/ClickHouse schema details
|
|
54
|
+
- chart/deck panels
|
|
55
|
+
- macro surface kinds and panel ids
|
|
56
|
+
- macro system prompt content
|
|
57
|
+
- macro provisioning templates / Python SDK / builtins
|
|
58
|
+
- macro event names and payload meanings
|
|
59
|
+
|
|
60
|
+
Workspace/generic helpers may own:
|
|
61
|
+
|
|
62
|
+
- simple plugin composition from child plugins
|
|
63
|
+
- generic REST-to-`ExplorerAdapter` plumbing
|
|
64
|
+
- generic static rows adapter plumbing
|
|
65
|
+
- generic `openSurface` command helper
|
|
66
|
+
- generic surface resolver builders
|
|
67
|
+
- generic typed event namespace helpers
|
|
68
|
+
- generic path display normalization helpers, where safe
|
|
69
|
+
|
|
70
|
+
Avoid a large enhancer/mixin framework. Prefer normal plugins composing other
|
|
71
|
+
normal plugins through one small `composePlugins()` helper.
|
|
72
|
+
|
|
73
|
+
## Preferred Generic Plugin Composition Model
|
|
74
|
+
|
|
75
|
+
The simplest general model is: plugins compose plugins.
|
|
76
|
+
|
|
77
|
+
Instead of a broad enhancer framework such as `withPanels()`,
|
|
78
|
+
`withCatalogs()`, `withDataCatalog()`, etc., add one small helper that flattens
|
|
79
|
+
child plugin contributions into a parent plugin:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
export function composePlugins(options: {
|
|
83
|
+
id: string;
|
|
84
|
+
label?: string;
|
|
85
|
+
plugins: Plugin[];
|
|
86
|
+
outputs?: PluginOutput[];
|
|
87
|
+
panels?: PanelConfig[];
|
|
88
|
+
catalogs?: CatalogConfig[];
|
|
89
|
+
commands?: CommandConfig[];
|
|
90
|
+
adoptOutputs?: boolean;
|
|
91
|
+
}): Plugin;
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Mental model:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
const macroPlugin = composePlugins({
|
|
98
|
+
id: MACRO_PLUGIN_ID,
|
|
99
|
+
label: "Macro",
|
|
100
|
+
plugins: [macroPanelsPlugin, macroSurfacesPlugin, macroSeriesCatalogPlugin],
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
This keeps every feature behind the same plugin interface. Data catalog remains
|
|
105
|
+
a plugin; surfaces can be a tiny plugin; app domains can publish smaller plugin
|
|
106
|
+
fragments and compose them into one app plugin.
|
|
107
|
+
|
|
108
|
+
### Composition semantics
|
|
109
|
+
|
|
110
|
+
Default recommendation: **parent adopts child outputs**.
|
|
111
|
+
|
|
112
|
+
When `adoptOutputs !== false`, composed outputs should register as owned by the
|
|
113
|
+
parent plugin id at bootstrap time. This matches app-domain expectations: the
|
|
114
|
+
macro catalog is part of the Macro plugin even if it was created by a reusable
|
|
115
|
+
data catalog plugin factory.
|
|
116
|
+
|
|
117
|
+
When `adoptOutputs === false`, child plugin ids are preserved. This is useful
|
|
118
|
+
for independent third-party plugin bundles where inspector/debug output should
|
|
119
|
+
show original child ownership.
|
|
120
|
+
|
|
121
|
+
`composePlugins()` should:
|
|
122
|
+
|
|
123
|
+
- flatten `outputs`, legacy `panels`, `catalogs`, `commands`, bindings, and
|
|
124
|
+
other contribution arrays from child plugins
|
|
125
|
+
- preserve deterministic order: child plugins in array order, then explicit
|
|
126
|
+
parent outputs
|
|
127
|
+
- detect duplicate contribution ids early where possible
|
|
128
|
+
- not invent a new lifecycle model
|
|
129
|
+
- not deeply clone components/functions
|
|
130
|
+
- leave server plugin composition as a separate concern unless the same simple
|
|
131
|
+
shape naturally applies there
|
|
132
|
+
|
|
133
|
+
This general composition helper can replace many ad hoc `appendXOutputs()`
|
|
134
|
+
patterns over time while keeping existing helpers as compatibility wrappers.
|
|
135
|
+
|
|
136
|
+
## File-by-file Audit
|
|
137
|
+
|
|
138
|
+
### `constants.ts`
|
|
139
|
+
|
|
140
|
+
Current:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
export const MACRO_PLUGIN_ID = "boring-macro";
|
|
144
|
+
export const MACRO_CHART_PANEL_ID = "chart-canvas";
|
|
145
|
+
export const MACRO_DECK_PANEL_ID = "deck";
|
|
146
|
+
export const MACRO_SERIES_SURFACE_RESOLVER_ID = "boring-macro-series";
|
|
147
|
+
export const MACRO_DECK_SURFACE_RESOLVER_ID = "boring-macro-deck-path";
|
|
148
|
+
export const MACRO_OPEN_SERIES_SURFACE_KIND = "macro.open-series";
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Decision: keep in macro.
|
|
152
|
+
|
|
153
|
+
Potential generic improvement: add optional helper conventions for deriving ids:
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
createPluginIds("boring-macro", {
|
|
157
|
+
panels: ["chart-canvas", "deck"],
|
|
158
|
+
surfaces: ["open-series"],
|
|
159
|
+
resolvers: ["series", "deck-path"],
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Do **not** implement unless id drift becomes common. Current constants are clear
|
|
164
|
+
and low boilerplate.
|
|
165
|
+
|
|
166
|
+
### `panels.tsx`
|
|
167
|
+
|
|
168
|
+
Current boilerplate:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
export const chartCanvasPanel = definePanel({
|
|
172
|
+
id: MACRO_CHART_PANEL_ID,
|
|
173
|
+
title: "Chart",
|
|
174
|
+
component: ChartCanvasPane,
|
|
175
|
+
placement: "center",
|
|
176
|
+
source: "app",
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Decision: mostly keep. `definePanel()` already handles generic panel shape.
|
|
181
|
+
|
|
182
|
+
Possible helper only if repeated heavily:
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
createCenterPanel({ id, title, component, source: "app" });
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Not worth extracting now. Panel declarations are readable and explicit.
|
|
189
|
+
|
|
190
|
+
### `index.tsx`
|
|
191
|
+
|
|
192
|
+
Current responsibilities:
|
|
193
|
+
|
|
194
|
+
- exports `MacroStandaloneDeckRoute`
|
|
195
|
+
- declares `macroChatSuggestions`
|
|
196
|
+
- declares shell options
|
|
197
|
+
- composes plugin outputs:
|
|
198
|
+
- chart panel
|
|
199
|
+
- deck panel
|
|
200
|
+
- macro surface resolvers
|
|
201
|
+
- data catalog outputs via `appendDataCatalogOutputs()`
|
|
202
|
+
|
|
203
|
+
Good current pattern:
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
return appendDataCatalogOutputs(
|
|
207
|
+
plugin,
|
|
208
|
+
createMacroSeriesDataCatalogOptions(onSeriesSelect),
|
|
209
|
+
);
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Decision: keep macro composition here until `composePlugins()` exists, then
|
|
213
|
+
prefer composing smaller plugin fragments:
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
const macroPanelsPlugin = definePlugin({
|
|
217
|
+
id: "macro-panels",
|
|
218
|
+
outputs: [
|
|
219
|
+
{ type: "panel", panel: chartCanvasPanel },
|
|
220
|
+
{ type: "panel", panel: deckPanel },
|
|
221
|
+
],
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const macroSurfacesPlugin = definePlugin({
|
|
225
|
+
id: "macro-surfaces",
|
|
226
|
+
outputs: macroSurfaceOutputs,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const macroSeriesCatalogPlugin = createDataCatalogPlugin({
|
|
230
|
+
...createMacroSeriesDataCatalogOptions(onSeriesSelect),
|
|
231
|
+
pluginId: "macro-series-catalog",
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
return composePlugins({
|
|
235
|
+
id: MACRO_PLUGIN_ID,
|
|
236
|
+
label: "Macro",
|
|
237
|
+
plugins: [macroPanelsPlugin, macroSurfacesPlugin, macroSeriesCatalogPlugin],
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
This is simpler than adding many specialized enhancer functions. Existing
|
|
242
|
+
`appendDataCatalogOutputs()` can remain as compatibility sugar implemented on
|
|
243
|
+
top of `composePlugins()` later.
|
|
244
|
+
|
|
245
|
+
Possible generic improvements:
|
|
246
|
+
|
|
247
|
+
1. **Chat suggestion type export**
|
|
248
|
+
|
|
249
|
+
Macro defines a local `MacroChatSuggestion` that probably mirrors a workspace
|
|
250
|
+
chat suggestion shape. If `WorkspaceProvider` already owns a compatible
|
|
251
|
+
public type, macro should import it. If not, expose one:
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
export interface ChatSuggestion {
|
|
255
|
+
label: string;
|
|
256
|
+
hint?: string;
|
|
257
|
+
icon?: ComponentType<{ className?: string }>;
|
|
258
|
+
prompt?: string;
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
This avoids every app re-declaring the same suggestion type.
|
|
263
|
+
|
|
264
|
+
2. **Shell options type**
|
|
265
|
+
|
|
266
|
+
`macroShellOptions` is app-shell config, not plugin-domain behavior. If more
|
|
267
|
+
apps duplicate this object shape, expose a `WorkspaceShellOptions` type from
|
|
268
|
+
workspace/app composition docs. Do not move macro values.
|
|
269
|
+
|
|
270
|
+
### `catalogs.ts`
|
|
271
|
+
|
|
272
|
+
Current responsibilities:
|
|
273
|
+
|
|
274
|
+
- owns macro facets: frequency/source
|
|
275
|
+
- creates macro `CreateDataCatalogOutputsOptions`
|
|
276
|
+
- wires adapter, labels, groupBy, drag payload, select callback
|
|
277
|
+
|
|
278
|
+
Decision: keep macro-specific options here, but generic helpers can reduce
|
|
279
|
+
boilerplate.
|
|
280
|
+
|
|
281
|
+
Generic candidates:
|
|
282
|
+
|
|
283
|
+
1. **Data catalog preset helper**
|
|
284
|
+
|
|
285
|
+
Current repeated shape:
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
{
|
|
289
|
+
id,
|
|
290
|
+
label: "Data",
|
|
291
|
+
adapter,
|
|
292
|
+
facets,
|
|
293
|
+
groupBy,
|
|
294
|
+
onSelect,
|
|
295
|
+
leftTabId,
|
|
296
|
+
leftTabTitle: "Data",
|
|
297
|
+
catalogId,
|
|
298
|
+
catalogLabel,
|
|
299
|
+
includeVisualizationPanel: false,
|
|
300
|
+
emptyState,
|
|
301
|
+
searchPlaceholder,
|
|
302
|
+
getDragPayload,
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Could become:
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
createDataCatalogPreset({
|
|
310
|
+
id: "macro-series",
|
|
311
|
+
catalogLabel: "Macro Series",
|
|
312
|
+
adapter: macroAdapter,
|
|
313
|
+
facets: MACRO_FACETS,
|
|
314
|
+
groupBy: "frequency",
|
|
315
|
+
onSelect,
|
|
316
|
+
emptyState: "No series match",
|
|
317
|
+
searchPlaceholder: "Search...",
|
|
318
|
+
dragMimeType: "text/series-id",
|
|
319
|
+
visualization: false,
|
|
320
|
+
});
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Owner: `dataCatalogPlugin`, not macro.
|
|
324
|
+
|
|
325
|
+
2. **Drag payload helper**
|
|
326
|
+
|
|
327
|
+
```ts
|
|
328
|
+
getDragPayload: createTextDragPayload("text/series-id", (row) => row.id);
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Owner: future `explorerPlugin` or `DataExplorer/adapters`.
|
|
332
|
+
|
|
333
|
+
3. **Facet config helper**
|
|
334
|
+
|
|
335
|
+
`MACRO_FACETS` is domain-specific, but a helper can encode order + formatter:
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
facet("frequency", "Frequency", { order, labels: FREQ_LABELS });
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Only extract if multiple apps repeat this pattern. Low priority.
|
|
342
|
+
|
|
343
|
+
### `data/macroSeriesAdapter.ts`
|
|
344
|
+
|
|
345
|
+
Current responsibilities:
|
|
346
|
+
|
|
347
|
+
- maps macro API rows to `ExplorerRow`
|
|
348
|
+
- builds query strings
|
|
349
|
+
- fetches `/api/macro/catalog` and `/api/macro/facets`
|
|
350
|
+
- maps explorer search args to query params
|
|
351
|
+
- maps facets response
|
|
352
|
+
|
|
353
|
+
Decision: extract generic REST adapter plumbing; keep macro row mapping and URLs
|
|
354
|
+
in macro.
|
|
355
|
+
|
|
356
|
+
Generic helper proposal:
|
|
357
|
+
|
|
358
|
+
```ts
|
|
359
|
+
export function createRestExplorerAdapter<ApiRow, FacetsResponse>(options: {
|
|
360
|
+
searchUrl: string | ((args: ExplorerSearchArgs) => string);
|
|
361
|
+
facetsUrl?: string | ((args: ExplorerFacetsArgs) => string);
|
|
362
|
+
mapRow: (row: ApiRow) => ExplorerRow;
|
|
363
|
+
mapSearchArgs?: (
|
|
364
|
+
args: ExplorerSearchArgs,
|
|
365
|
+
) => Record<string, string | number | string[] | undefined>;
|
|
366
|
+
mapFacetArgs?: (
|
|
367
|
+
args: ExplorerFacetsArgs,
|
|
368
|
+
) => Record<string, string | number | string[] | undefined>;
|
|
369
|
+
mapSearchResponse?: (json: unknown) => {
|
|
370
|
+
items: ApiRow[];
|
|
371
|
+
total: number;
|
|
372
|
+
hasMore: boolean;
|
|
373
|
+
};
|
|
374
|
+
mapFacetsResponse?: (json: FacetsResponse) => Facets;
|
|
375
|
+
fetch?: typeof globalThis.fetch;
|
|
376
|
+
}): ExplorerAdapter;
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Macro after extraction:
|
|
380
|
+
|
|
381
|
+
```ts
|
|
382
|
+
export function createMacroSeriesAdapter(): ExplorerAdapter {
|
|
383
|
+
return createRestExplorerAdapter<CatalogItem, FacetsResponse>({
|
|
384
|
+
searchUrl: "/api/macro/catalog",
|
|
385
|
+
facetsUrl: "/api/macro/facets",
|
|
386
|
+
mapRow: toMacroSeriesRow,
|
|
387
|
+
mapSearchArgs: (args) => ({
|
|
388
|
+
q: args.query || undefined,
|
|
389
|
+
offset: args.offset,
|
|
390
|
+
limit: args.limit,
|
|
391
|
+
group: args.group?.value,
|
|
392
|
+
frequency: args.filters.frequency,
|
|
393
|
+
source: args.filters.source,
|
|
394
|
+
}),
|
|
395
|
+
mapFacetArgs: (args) => ({
|
|
396
|
+
frequency: args.filters.frequency,
|
|
397
|
+
source: args.filters.source,
|
|
398
|
+
}),
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Also extract generic query helper:
|
|
404
|
+
|
|
405
|
+
```ts
|
|
406
|
+
toQueryString(params: Record<string, string | number | string[] | undefined>): string
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
Owner: `explorerPlugin/adapters.ts` once created. Temporary owner can be
|
|
410
|
+
`front/components/DataExplorer/adapters.ts`.
|
|
411
|
+
|
|
412
|
+
Risk: don't overfit response shape. Keep mapper hooks explicit.
|
|
413
|
+
|
|
414
|
+
### `data/macroSeriesUi.ts`
|
|
415
|
+
|
|
416
|
+
Current responsibilities:
|
|
417
|
+
|
|
418
|
+
- `FREQ_LABELS`: macro/domain display labels
|
|
419
|
+
- `SERIES_COLORS`: chart palette
|
|
420
|
+
- `formatSeriesValue`: numeric display helper
|
|
421
|
+
- `openSeriesPane`: posts openSurface command for macro series
|
|
422
|
+
|
|
423
|
+
Decision:
|
|
424
|
+
|
|
425
|
+
- keep labels/colors in macro
|
|
426
|
+
- consider moving `formatSeriesValue` only if many apps need generic numeric
|
|
427
|
+
compact formatting; otherwise keep macro
|
|
428
|
+
- extract generic `openSurface` command builders
|
|
429
|
+
|
|
430
|
+
Generic open helpers:
|
|
431
|
+
|
|
432
|
+
```ts
|
|
433
|
+
export function openSurface(options: {
|
|
434
|
+
kind: string;
|
|
435
|
+
target: string;
|
|
436
|
+
meta?: Record<string, unknown>;
|
|
437
|
+
}): void;
|
|
438
|
+
|
|
439
|
+
export function createOpenSurfaceHandler<Input>(options: {
|
|
440
|
+
kind: string;
|
|
441
|
+
getTarget: (input: Input) => string | undefined;
|
|
442
|
+
getMeta?: (input: Input) => Record<string, unknown> | undefined;
|
|
443
|
+
}): (input: Input) => void;
|
|
444
|
+
|
|
445
|
+
export function createOpenSurfaceRowHandler(options: {
|
|
446
|
+
kind: string;
|
|
447
|
+
catalogId?: string;
|
|
448
|
+
getTarget?: (row: ExplorerRow) => string | undefined;
|
|
449
|
+
getMeta?: (row: ExplorerRow) => Record<string, unknown> | undefined;
|
|
450
|
+
}): (row: ExplorerRow) => void;
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
Macro after extraction:
|
|
454
|
+
|
|
455
|
+
```ts
|
|
456
|
+
export const openSeriesPane = createOpenSurfaceHandler<string>({
|
|
457
|
+
kind: MACRO_OPEN_SERIES_SURFACE_KIND,
|
|
458
|
+
getTarget: (seriesId) => seriesId.trim() || undefined,
|
|
459
|
+
getMeta: (_seriesId, opts) => ... // if supporting options, use a two-arg helper
|
|
460
|
+
})
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
Because `openSeriesPane(seriesId, opts)` currently takes two args, either keep a
|
|
464
|
+
small macro wrapper or make the generic helper support tuple input. Prefer small
|
|
465
|
+
wrapper for readability.
|
|
466
|
+
|
|
467
|
+
### `surfaceResolver.ts`
|
|
468
|
+
|
|
469
|
+
Current responsibilities:
|
|
470
|
+
|
|
471
|
+
- generic target trimming / title fallback / panel resolution boilerplate
|
|
472
|
+
- generic path normalization / basename
|
|
473
|
+
- macro-specific series routing
|
|
474
|
+
- macro-specific deck markdown path matching
|
|
475
|
+
|
|
476
|
+
Decision: best immediate extraction candidate after explorer adapter.
|
|
477
|
+
|
|
478
|
+
Generic helper 1: target surface resolver
|
|
479
|
+
|
|
480
|
+
```ts
|
|
481
|
+
export function createTargetSurfaceResolver<
|
|
482
|
+
Target extends string = string,
|
|
483
|
+
>(options: {
|
|
484
|
+
id: string;
|
|
485
|
+
source?: PanelConfig["source"];
|
|
486
|
+
kind: string;
|
|
487
|
+
component: string;
|
|
488
|
+
score?: number;
|
|
489
|
+
normalizeTarget?: (target: string) => Target | undefined;
|
|
490
|
+
getPanelId?: (target: Target, request: SurfaceOpenRequest) => string;
|
|
491
|
+
getTitle?: (
|
|
492
|
+
target: Target,
|
|
493
|
+
request: SurfaceOpenRequest,
|
|
494
|
+
) => string | undefined;
|
|
495
|
+
getParams?: (
|
|
496
|
+
target: Target,
|
|
497
|
+
request: SurfaceOpenRequest,
|
|
498
|
+
) => Record<string, unknown>;
|
|
499
|
+
}): SurfaceResolverConfig;
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
Generic helper 2: path surface resolver
|
|
503
|
+
|
|
504
|
+
```ts
|
|
505
|
+
export function createPathSurfaceResolver(options: {
|
|
506
|
+
id: string;
|
|
507
|
+
source?: PanelConfig["source"];
|
|
508
|
+
kind?: string; // default WORKSPACE_OPEN_PATH_SURFACE_KIND
|
|
509
|
+
component: string;
|
|
510
|
+
score?: number;
|
|
511
|
+
matches: (path: string, request: SurfaceOpenRequest) => boolean;
|
|
512
|
+
getPanelId?: (path: string, request: SurfaceOpenRequest) => string;
|
|
513
|
+
getTitle?: (path: string, request: SurfaceOpenRequest) => string;
|
|
514
|
+
getParams?: (
|
|
515
|
+
path: string,
|
|
516
|
+
request: SurfaceOpenRequest,
|
|
517
|
+
) => Record<string, unknown>;
|
|
518
|
+
}): SurfaceResolverConfig;
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
Generic helper 3: safe display path helpers
|
|
522
|
+
|
|
523
|
+
```ts
|
|
524
|
+
normalizeSurfacePath(path: string): string
|
|
525
|
+
basename(path: string): string
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
Important: these are **not** security validators. Path validation remains the
|
|
529
|
+
adapter/server/filesystem plugin's job.
|
|
530
|
+
|
|
531
|
+
Macro after extraction:
|
|
532
|
+
|
|
533
|
+
```ts
|
|
534
|
+
export const macroSurfaceOutputs = surfaceResolverOutputs([
|
|
535
|
+
createTargetSurfaceResolver({
|
|
536
|
+
id: MACRO_SERIES_SURFACE_RESOLVER_ID,
|
|
537
|
+
source: "app",
|
|
538
|
+
kind: MACRO_OPEN_SERIES_SURFACE_KIND,
|
|
539
|
+
component: MACRO_CHART_PANEL_ID,
|
|
540
|
+
normalizeTarget: trimNonEmpty,
|
|
541
|
+
getPanelId: (seriesId) => `chart:${seriesId}`,
|
|
542
|
+
getTitle: (seriesId, request) =>
|
|
543
|
+
readStringMeta(request.meta, "title") ?? seriesId,
|
|
544
|
+
getParams: (seriesId) => ({ seriesId }),
|
|
545
|
+
}),
|
|
546
|
+
createPathSurfaceResolver({
|
|
547
|
+
id: MACRO_DECK_SURFACE_RESOLVER_ID,
|
|
548
|
+
source: "app",
|
|
549
|
+
component: MACRO_DECK_PANEL_ID,
|
|
550
|
+
score: 10,
|
|
551
|
+
matches: isDeckMarkdownPath,
|
|
552
|
+
getPanelId: (path) => `file:${path}`,
|
|
553
|
+
getTitle: basename,
|
|
554
|
+
getParams: (path) => ({ path }),
|
|
555
|
+
}),
|
|
556
|
+
]);
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
Could also add:
|
|
560
|
+
|
|
561
|
+
```ts
|
|
562
|
+
surfaceResolverOutputs(resolvers): PluginOutput[]
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
Owner: `front/registry/surfaceResolverHelpers.ts` or future
|
|
566
|
+
`plugins/explorerPlugin/surface.ts`. Since these helpers are not explorer-only,
|
|
567
|
+
prefer `front/registry` or a new `front/surface` module.
|
|
568
|
+
|
|
569
|
+
### `data/macroSeriesData.ts`
|
|
570
|
+
|
|
571
|
+
Current responsibilities:
|
|
572
|
+
|
|
573
|
+
- fetch series payload by id
|
|
574
|
+
- in-memory cache
|
|
575
|
+
- in-flight de-dup
|
|
576
|
+
- reset cache
|
|
577
|
+
|
|
578
|
+
Decision: possible generic cache helper, but not priority.
|
|
579
|
+
|
|
580
|
+
Generic helper:
|
|
581
|
+
|
|
582
|
+
```ts
|
|
583
|
+
createResourceCache<Key, Value>({
|
|
584
|
+
load: (key) => Promise<Value>,
|
|
585
|
+
keyToString?: (key) => string,
|
|
586
|
+
})
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
Macro after extraction:
|
|
590
|
+
|
|
591
|
+
```ts
|
|
592
|
+
const seriesCache = createResourceCache({
|
|
593
|
+
load: async (seriesId: string) => { ...fetch macro series... },
|
|
594
|
+
})
|
|
595
|
+
export const fetchMacroSeries = seriesCache.fetch
|
|
596
|
+
export const clearMacroSeriesCache = seriesCache.clear
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
Risk: introducing a cache abstraction may hide app-specific invalidation rules.
|
|
600
|
+
Leave until another plugin duplicates it.
|
|
601
|
+
|
|
602
|
+
### `data/macroSeriesTypes.ts`
|
|
603
|
+
|
|
604
|
+
Decision: keep in macro. Domain payload.
|
|
605
|
+
|
|
606
|
+
### `panels/ChartCanvasPane.tsx` and `panels/DeckPane.tsx`
|
|
607
|
+
|
|
608
|
+
Decision: keep in macro. Domain UI.
|
|
609
|
+
|
|
610
|
+
Potential generic extraction only if repeated:
|
|
611
|
+
|
|
612
|
+
- empty/loading/error panel state components
|
|
613
|
+
- chart color palette? no; macro-specific enough
|
|
614
|
+
- markdown deck embed mechanics? likely domain/app-specific until a separate
|
|
615
|
+
deck plugin exists
|
|
616
|
+
|
|
617
|
+
### `server/index.ts`
|
|
618
|
+
|
|
619
|
+
Current responsibilities:
|
|
620
|
+
|
|
621
|
+
- server plugin object
|
|
622
|
+
- routes registration
|
|
623
|
+
- provisioning declarations
|
|
624
|
+
- macro system prompt
|
|
625
|
+
- macro tools
|
|
626
|
+
|
|
627
|
+
Decision: keep macro domain, but generic helpers can reduce boilerplate.
|
|
628
|
+
|
|
629
|
+
Generic candidates:
|
|
630
|
+
|
|
631
|
+
1. **Server plugin factory helper**
|
|
632
|
+
|
|
633
|
+
```ts
|
|
634
|
+
createServerPlugin({
|
|
635
|
+
id,
|
|
636
|
+
label,
|
|
637
|
+
routes,
|
|
638
|
+
agentTools,
|
|
639
|
+
provisioning,
|
|
640
|
+
systemPrompt,
|
|
641
|
+
});
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
Only useful if many server plugins repeat the exact shape. Low priority.
|
|
645
|
+
|
|
646
|
+
2. **System prompt builder for surface contracts**
|
|
647
|
+
|
|
648
|
+
Macro prompt manually documents:
|
|
649
|
+
|
|
650
|
+
```txt
|
|
651
|
+
call exec_ui with kind "openSurface" and params { kind, target, meta }
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
Data catalog server helper already generates similar prompt text. Extract a
|
|
655
|
+
shared prompt helper:
|
|
656
|
+
|
|
657
|
+
```ts
|
|
658
|
+
createOpenSurfacePrompt({
|
|
659
|
+
surfaceKind: MACRO_OPEN_SERIES_SURFACE_KIND,
|
|
660
|
+
targetDescription: "series_id",
|
|
661
|
+
metaExample: "{ title }",
|
|
662
|
+
});
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
Owner: workspace server UI-control or dataCatalogPlugin server utilities.
|
|
666
|
+
|
|
667
|
+
3. **Provisioning type**
|
|
668
|
+
|
|
669
|
+
Macro declares local `MacroProvisioningContribution`. If workspace server plugin
|
|
670
|
+
already has a canonical provisioning type, import it. If not, add one. This
|
|
671
|
+
prevents local drift in app server plugins.
|
|
672
|
+
|
|
673
|
+
### `server/tools/macroTools.ts`
|
|
674
|
+
|
|
675
|
+
Current responsibilities:
|
|
676
|
+
|
|
677
|
+
- ClickHouse read-only SQL guard
|
|
678
|
+
- tool result formatting
|
|
679
|
+
- macro search/data/derived tools
|
|
680
|
+
- SQL hinting
|
|
681
|
+
|
|
682
|
+
Decision: mostly keep in macro.
|
|
683
|
+
|
|
684
|
+
Generic candidates:
|
|
685
|
+
|
|
686
|
+
1. `textResult()` / `errorResult()` helpers already exist in other workspace
|
|
687
|
+
server code patterns. Export shared helpers from a server utilities module:
|
|
688
|
+
|
|
689
|
+
```ts
|
|
690
|
+
textToolResult(text);
|
|
691
|
+
errorToolResult(text);
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
2. read-only SQL guard might be reusable for DB plugins, but ClickHouse hints
|
|
695
|
+
are macro-specific. If extracted, keep generic guard separate:
|
|
696
|
+
|
|
697
|
+
```ts
|
|
698
|
+
assertReadonlySql(sql, { allowed: ["SELECT", "WITH", ...] })
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
Low priority unless another DB plugin appears.
|
|
702
|
+
|
|
703
|
+
### `server/routes/macro.ts`
|
|
704
|
+
|
|
705
|
+
Current responsibilities:
|
|
706
|
+
|
|
707
|
+
- query param parsing/clamping
|
|
708
|
+
- auth dev bypass
|
|
709
|
+
- macro catalog/facets/series/deck/refresh routes
|
|
710
|
+
- filesystem deck route operations
|
|
711
|
+
|
|
712
|
+
Generic candidates:
|
|
713
|
+
|
|
714
|
+
1. Query helpers:
|
|
715
|
+
|
|
716
|
+
```ts
|
|
717
|
+
parseCommaSep();
|
|
718
|
+
clampInt();
|
|
719
|
+
optionalInt();
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
Could move to workspace server route utils if repeated. Low priority.
|
|
723
|
+
|
|
724
|
+
2. Catalog/facets REST contract:
|
|
725
|
+
|
|
726
|
+
If `createRestExplorerAdapter` becomes a workspace helper, document the expected
|
|
727
|
+
server response shape `{ items, total, hasMore }`. Do not move macro routes.
|
|
728
|
+
|
|
729
|
+
3. Dev localhost bypass is app/server policy. Do not move unless core/cloud adds
|
|
730
|
+
a canonical dev-auth helper.
|
|
731
|
+
|
|
732
|
+
## Event Handling Audit
|
|
733
|
+
|
|
734
|
+
Macro currently uses workspace event bus indirectly:
|
|
735
|
+
|
|
736
|
+
- `openSeriesPane()` posts `workspace:ui.command` through `postUiCommand()`.
|
|
737
|
+
- tests subscribe to `events.on("workspace:ui.command", ...)`.
|
|
738
|
+
- filesystem/agent events are owned by workspace/filesystem plugin, not macro.
|
|
739
|
+
|
|
740
|
+
No macro-specific event namespace exists yet.
|
|
741
|
+
|
|
742
|
+
Generic improvements:
|
|
743
|
+
|
|
744
|
+
1. **Avoid raw event-name strings in tests**
|
|
745
|
+
|
|
746
|
+
Test currently uses:
|
|
747
|
+
|
|
748
|
+
```ts
|
|
749
|
+
const UI_COMMAND_EVENT = "workspace:ui.command";
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
Prefer exporting/importing the canonical event key if available:
|
|
753
|
+
|
|
754
|
+
```ts
|
|
755
|
+
import { workspaceEvents } from "@boring/workspace/events";
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
If not exported, expose it. Raw event strings in app tests drift easily.
|
|
759
|
+
|
|
760
|
+
2. **Plugin event namespace helper**
|
|
761
|
+
|
|
762
|
+
When macro adds domain events, use:
|
|
763
|
+
|
|
764
|
+
```ts
|
|
765
|
+
export const macroEvents = createPluginEventNamespace(MACRO_PLUGIN_ID, {
|
|
766
|
+
seriesOpened: "series.opened",
|
|
767
|
+
transformCompleted: "transform.completed",
|
|
768
|
+
deckSaved: "deck.saved",
|
|
769
|
+
});
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
This helper should:
|
|
773
|
+
|
|
774
|
+
- prefix event names with plugin id
|
|
775
|
+
- preserve literal types
|
|
776
|
+
- avoid collisions
|
|
777
|
+
- avoid importing app/domain events into workspace core
|
|
778
|
+
|
|
779
|
+
3. **Typed plugin event map augmentation**
|
|
780
|
+
|
|
781
|
+
If workspace supports augmentable event maps, macro can own event payload types:
|
|
782
|
+
|
|
783
|
+
```ts
|
|
784
|
+
declare module "@boring/workspace/events" {
|
|
785
|
+
interface WorkspacePluginEventMap {
|
|
786
|
+
"boring-macro:series.opened": { seriesId: string; title?: string };
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
Only implement when macro actually emits domain events.
|
|
792
|
+
|
|
793
|
+
## Surface Handling Audit
|
|
794
|
+
|
|
795
|
+
Surface handling is the biggest near-term boilerplate win.
|
|
796
|
+
|
|
797
|
+
Current macro has two resolver patterns:
|
|
798
|
+
|
|
799
|
+
1. target resolver: surface kind + string target -> panel
|
|
800
|
+
2. path resolver: workspace path surface + path predicate -> panel
|
|
801
|
+
|
|
802
|
+
These patterns will recur in Feret:
|
|
803
|
+
|
|
804
|
+
- ingredient id -> ingredient detail panel
|
|
805
|
+
- formulation id -> formulation panel
|
|
806
|
+
- source file path -> extraction review panel
|
|
807
|
+
- project path -> custom markdown/source preview panel
|
|
808
|
+
|
|
809
|
+
Generic resolver helpers should be introduced before Feret repeats the macro
|
|
810
|
+
boilerplate.
|
|
811
|
+
|
|
812
|
+
Recommended owner:
|
|
813
|
+
|
|
814
|
+
```txt
|
|
815
|
+
packages/workspace/src/front/registry/surfaceResolverHelpers.ts
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
Exports from package root:
|
|
819
|
+
|
|
820
|
+
```ts
|
|
821
|
+
createTargetSurfaceResolver;
|
|
822
|
+
createPathSurfaceResolver;
|
|
823
|
+
surfaceResolverOutputs;
|
|
824
|
+
normalizeSurfacePath;
|
|
825
|
+
surfaceBasename;
|
|
826
|
+
readStringMeta;
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
Risk: this lives under `front/registry` but helpers may be useful in app plugins.
|
|
830
|
+
That is acceptable because app plugins already import `definePanel` from front
|
|
831
|
+
registry types via package root. Do not put these in `shared/` if they depend on
|
|
832
|
+
React/panel component types. If kept type-only and browser-safe, `shared/types`
|
|
833
|
+
may be okay later.
|
|
834
|
+
|
|
835
|
+
## Data Catalog / Explorer Integration Audit
|
|
836
|
+
|
|
837
|
+
Macro already correctly composes data catalog outputs:
|
|
838
|
+
|
|
839
|
+
```ts
|
|
840
|
+
appendDataCatalogOutputs(
|
|
841
|
+
plugin,
|
|
842
|
+
createMacroSeriesDataCatalogOptions(onSeriesSelect),
|
|
843
|
+
);
|
|
844
|
+
```
|
|
845
|
+
|
|
846
|
+
Better integration opportunities:
|
|
847
|
+
|
|
848
|
+
1. `composePlugins()` so macro can compose a real `createDataCatalogPlugin()`
|
|
849
|
+
child plugin rather than mutating/appending outputs onto a base plugin.
|
|
850
|
+
2. `createDataCatalogPreset()` only if option boilerplate remains noisy after
|
|
851
|
+
plugin composition.
|
|
852
|
+
3. `createRestExplorerAdapter()` for `/catalog` + `/facets` endpoints.
|
|
853
|
+
4. `createOpenSurfaceRowHandler()` for catalog row activation.
|
|
854
|
+
5. Future `explorerPlugin` owns explorer primitives and adapter helpers;
|
|
855
|
+
`dataCatalogPlugin` composes them.
|
|
856
|
+
|
|
857
|
+
Do not make macro depend directly on a future `explorerPlugin` if
|
|
858
|
+
`dataCatalogPlugin` can provide the right data-catalog specialization. Macro
|
|
859
|
+
should depend on the highest-level appropriate abstraction:
|
|
860
|
+
|
|
861
|
+
```txt
|
|
862
|
+
macro series catalog -> dataCatalogPlugin plugin/factory
|
|
863
|
+
macro custom project/tree explorer -> explorerPlugin plugin/factory
|
|
864
|
+
macro chart/deck panels -> macro-owned panels
|
|
865
|
+
```
|
|
866
|
+
|
|
867
|
+
## Proposed Extraction Order
|
|
868
|
+
|
|
869
|
+
### Step 1 — `composePlugins()`
|
|
870
|
+
|
|
871
|
+
Why first: it answers the general customization question without a complex
|
|
872
|
+
mixin/enhancer framework. Plugins can build on other plugins through the same
|
|
873
|
+
interface they already expose.
|
|
874
|
+
|
|
875
|
+
Add `composePlugins()` in the shared plugin model and migrate macro composition
|
|
876
|
+
only if it makes the file simpler. Keep `appendDataCatalogOutputs()` as a
|
|
877
|
+
compatibility helper.
|
|
878
|
+
|
|
879
|
+
Tests:
|
|
880
|
+
|
|
881
|
+
- composition flattens child outputs in order
|
|
882
|
+
- duplicate ids are caught or warned consistently
|
|
883
|
+
- adopted ownership defaults to parent plugin id
|
|
884
|
+
- `adoptOutputs: false` preserves child plugin ids
|
|
885
|
+
- macro plugin output order remains stable
|
|
886
|
+
|
|
887
|
+
### Step 2 — Surface resolver helpers
|
|
888
|
+
|
|
889
|
+
Why first: high signal, low risk, clearly reduces macro/Feret boilerplate.
|
|
890
|
+
|
|
891
|
+
Add helpers and migrate macro `surfaceResolver.ts` to declare only rules.
|
|
892
|
+
|
|
893
|
+
Tests:
|
|
894
|
+
|
|
895
|
+
- macro resolver tests unchanged
|
|
896
|
+
- helper tests for kind mismatch, blank target, title fallback, path matching
|
|
897
|
+
|
|
898
|
+
### Step 3 — Open surface helpers
|
|
899
|
+
|
|
900
|
+
Add `openSurface()`, `createOpenSurfaceHandler()`, and
|
|
901
|
+
`createOpenSurfaceRowHandler()`.
|
|
902
|
+
|
|
903
|
+
Migrate `openSeriesPane()` to use helper internally, keeping public macro API
|
|
904
|
+
unchanged.
|
|
905
|
+
|
|
906
|
+
Tests:
|
|
907
|
+
|
|
908
|
+
- macro `openSeriesPane` test unchanged
|
|
909
|
+
- helper tests for trimming/empty target/meta
|
|
910
|
+
|
|
911
|
+
### Step 4 — REST explorer adapter helper
|
|
912
|
+
|
|
913
|
+
Add generic `createRestExplorerAdapter()` and `toQueryString()`.
|
|
914
|
+
|
|
915
|
+
Migrate `macroSeriesAdapter.ts` to keep only macro `toRow()` and arg mapping.
|
|
916
|
+
|
|
917
|
+
Tests:
|
|
918
|
+
|
|
919
|
+
- macro catalog adapter tests if present
|
|
920
|
+
- new helper tests for array query params, abort signal forwarding, non-OK
|
|
921
|
+
responses, response mapping
|
|
922
|
+
|
|
923
|
+
### Step 5 — Data catalog preset helper
|
|
924
|
+
|
|
925
|
+
Add `createDataCatalogPreset()` only if macro and playground both simplify after
|
|
926
|
+
`composePlugins()` exists. Plugin composition may make this unnecessary.
|
|
927
|
+
|
|
928
|
+
Migrate macro and playground catalog options only if the helper removes real
|
|
929
|
+
boilerplate.
|
|
930
|
+
|
|
931
|
+
Tests:
|
|
932
|
+
|
|
933
|
+
- existing data catalog plugin tests
|
|
934
|
+
- public API test for helper export if exported
|
|
935
|
+
|
|
936
|
+
### Step 6 — Event namespace helper
|
|
937
|
+
|
|
938
|
+
Add only when macro or Feret introduces first real domain event. Do not create
|
|
939
|
+
unused event abstraction.
|
|
940
|
+
|
|
941
|
+
## Do Not Extract Yet
|
|
942
|
+
|
|
943
|
+
- Chart/deck panels
|
|
944
|
+
- FRED frequency labels
|
|
945
|
+
- series colors
|
|
946
|
+
- ClickHouse services
|
|
947
|
+
- SQL tool hinting
|
|
948
|
+
- macro transform SDK/provisioning contents
|
|
949
|
+
- local shell option values
|
|
950
|
+
- deck path predicate (`deck/*.md` at root) beyond generic path resolver helper
|
|
951
|
+
|
|
952
|
+
## Acceptance Criteria
|
|
953
|
+
|
|
954
|
+
- Macro plugin file count does not grow just to satisfy abstraction.
|
|
955
|
+
- Macro `surfaceResolver.ts` becomes declarative: constants + predicates + helper
|
|
956
|
+
calls, no repeated resolver boilerplate.
|
|
957
|
+
- Macro catalog adapter keeps macro row mapping but delegates REST/query plumbing.
|
|
958
|
+
- Macro tests remain mostly unchanged, proving helpers preserve behavior.
|
|
959
|
+
- Feret can reuse the same helpers for ingredient/formulation/source-file
|
|
960
|
+
surfaces without depending on macro.
|
|
961
|
+
- Workspace does not learn FRED, ClickHouse, chart, deck, or macro series
|
|
962
|
+
semantics.
|