@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,387 @@
1
+ # Code ownership cleanup — keep packages reusable, apps lean
2
+
3
+ **Status:** draft
4
+ **Owners:** workspace, agent
5
+ **Last updated:** 2026-04-28
6
+
7
+ ## Problem
8
+
9
+ Tests for the `@boring/workspace` chat shell, dock, UI bridge, and `@boring/agent`
10
+ plumbing currently live in the standalone macro repo's `e2e/` directory (29
11
+ specs — see [`CONSOLIDATE_AND_STANDALONIZE.md`](../../../apps/boring-macro-v2/docs/CONSOLIDATE_AND_STANDALONIZE.md)
12
+ for how that repo merges into `apps/boring-macro-v2/e2e/`). That happened
13
+ because the macro app was the first non-trivial consumer and the tests caught real
14
+ bugs there. But the contents are mixed:
15
+
16
+ - Some specs assert macro-only things (FRED catalog, ChartCanvasPane tabs, DeckPane
17
+ edit/save, `/api/macro/*`).
18
+ - Others test things that should be true for every app built on `@boring/workspace`
19
+ (composer border state, dockview split-shrink, sidebar persistence keys,
20
+ ChatCenteredShell `appTitle`, the bridge accepting `openPanel`).
21
+
22
+ Two anti-patterns this creates:
23
+
24
+ 1. **Generic regressions only get caught by macro.** If a developer breaks
25
+ `SurfaceShell`'s persisted-width hydration or `ChatCenteredShell.appTitle`, the
26
+ first signal is a macro test failing — even though no macro code changed. The
27
+ blame trail and reproduction loop go through macro instead of workspace.
28
+ 2. **The playground accumulates app-shaped tests.** When we move generic specs
29
+ into `apps/workspace-playground/e2e/`, the playground's job creeps from "demo
30
+ the chat shell with a couple of mock adapters" to "be the test harness for
31
+ `@boring/workspace`." That's a different artifact: a demo wants to be small and
32
+ readable; a test harness wants to be deterministic and exhaustive. Stuffing
33
+ them into one app makes both worse.
34
+
35
+ We also currently lack a place to test combinations that aren't representative of
36
+ any one app — e.g. `extraPanels` filtering, the bridge dispatcher's
37
+ `!surface ? return : run` early-out, two-instance ChatCenteredShells with
38
+ distinct `storageKey`s. None of these belong in macro or in the playground.
39
+
40
+ ## Goal
41
+
42
+ Tests for `@boring/workspace` and `@boring/agent` primitives live INSIDE the
43
+ package they test, not in a downstream app. Child apps ship only the specs that
44
+ exercise app-specific contracts.
45
+
46
+ Also: define a concrete repo-wide cleanup boundary so code placement stays sane:
47
+
48
+ - `packages/*` = reusable primitives, adapters, and framework helpers.
49
+ - `apps/*` = composition, branding, product/domain logic, and deploy wiring.
50
+ - `apps/workspace-playground` remains demo-first and intentionally lean.
51
+
52
+ Concretely:
53
+
54
+ | Lives in… | Tests… | Hits… |
55
+ |---|---|---|
56
+ | `packages/workspace/e2e/` | chat shell layout, dock split/resize, UI bridge dispatcher, sidebar persistence, composer styling | Browser against a **fixture app** in `packages/workspace/e2e/fixture/` |
57
+ | `packages/agent/__tests__/` (existing) | `AgentTool` schema, harness, chat route, sessions | In-process |
58
+ | `apps/workspace-playground/` | nothing — playground stays a demo | n/a |
59
+ | `apps/boring-macro-v2/e2e/` | macro adapter shape, FRED catalog routes, ChartCanvasPane tabs, DeckPane edit/save, macro tool catalog | Browser against the macro dev server |
60
+
61
+ ## Why a fixture, not the playground
62
+
63
+ The playground's `App.tsx` exists to demonstrate the package's public API to a
64
+ human reading the source. It's tuned for readability:
65
+
66
+ ```tsx
67
+ <ChatCenteredShell
68
+ appTitle="Boring"
69
+ data={dataPaneConfig}
70
+ onSurfaceReady={(api) => { surfaceRef.current = api }}
71
+ />
72
+ ```
73
+
74
+ A test harness wants different things — known-fixed mock data, a stable storage
75
+ key namespace, deterministic agent responses, hooks for `data-testid`, easy
76
+ two-tab side-by-side scenarios. Adding any of those to the playground hurts its
77
+ "read this to learn the API" job. Forking a small fixture under
78
+ `packages/workspace/e2e/fixture/` lets the playground stay clean.
79
+
80
+ The fixture is roughly:
81
+
82
+ ```
83
+ packages/workspace/e2e/
84
+ fixture/
85
+ index.html
86
+ main.tsx
87
+ App.tsx # uses every public API surface we test, with stable test ids
88
+ mockSeriesAdapter.ts # deterministic, no random IDs
89
+ mockSessions.ts
90
+ helpers/
91
+ boot.ts # bootClean(page, seed) — same shape as macro's
92
+ pane.ts # openPaneViaBridge wait-for-tab pattern
93
+ specs/
94
+ composer-border.spec.ts
95
+ chat-shell-topbar.spec.ts
96
+ chat-suggestions.spec.ts # asserts defaultChatSuggestions render
97
+ dock-split-shrink.spec.ts
98
+ extra-panels.spec.ts
99
+ layout-persistence.spec.ts
100
+ bridge-openpanel.spec.ts
101
+ bridge-openfile.spec.ts
102
+ bridge-no-surface-noop.spec.ts # the early-out we hit
103
+ playwright.config.ts # webServer: vite serves fixture/ on a sibling port
104
+ package.json # internal — `pnpm --filter @boring/workspace test:e2e`
105
+ ```
106
+
107
+ The fixture is a real Vite app (because we need a real browser + real dockview),
108
+ but it's not exported from the package and never published. It runs only when
109
+ `pnpm test:e2e` is invoked from the workspace package.
110
+
111
+ ## Why not extend `boring-ui-v2/e2e/`
112
+
113
+ The repo root already has `boring-ui-v2/e2e/` with its own playwright config and
114
+ `spawnBackend` fixtures (`fixtures.ts`). That suite tests the agent backend with
115
+ isolated tmp workspaces and is geared toward **headless API + harness** behavior
116
+ (sessions persistence, mode flips, bridge protocol). It does not boot a frontend.
117
+
118
+ Mixing browser-driven UI specs into that infrastructure would duplicate Vite
119
+ boot logic and conflate "test the backend in isolation" with "test the chat
120
+ shell in a real DOM." Two suites with two configs and two purposes is cleaner:
121
+
122
+ - `boring-ui-v2/e2e/` — backend-focused, in-process, no browser, no Vite.
123
+ - `packages/workspace/e2e/` — browser-focused, real Vite + fixture app, real
124
+ dockview + recharts.
125
+
126
+ Each can evolve at its own pace.
127
+
128
+ ## Repository findings (2026-04-28 scan)
129
+
130
+ ### A) `apps/boring-macro-v2` (child app)
131
+
132
+ **Keep in app (macro-specific):**
133
+
134
+ - `src/server/routes/billing.ts` (Stripe tiers, checkout/webhook, quota policy)
135
+ - `src/server/routes/waitlist.ts` (macro landing behavior)
136
+ - macro tool semantics and system prompt in `src/server/tools/macroTools.ts`
137
+ - macro env policy in `src/server/config.ts` (`BM_*`, FRED defaults)
138
+
139
+ **Extract to reusable package surfaces:**
140
+
141
+ - `src/server/services/tabBus.ts` → **delete, not extract.** The
142
+ `@boring/workspace` UI bridge (`exec_ui` + `openPanel` command) already
143
+ covers everything tabBus does (push a "show this series" command from
144
+ the agent to the workbench). Migrate the macro call sites that still
145
+ push to tabBus over to `bridge.postCommand({kind:"openPanel", ...})`
146
+ and delete the file. Adding tabBus's API to workspace would duplicate
147
+ the bridge surface.
148
+ - reusable pieces from `src/server/services/clickhouse.ts`
149
+ (generic CH adapter/cache/read-only SQL guard pattern)
150
+ - reusable queue/rate-limit scaffolding from `src/server/services/fredRefresh.ts`
151
+ (keep FRED provider app-local; move generic queue core)
152
+ - small parsing helpers in `routes/macro.ts` (`clampInt`, `optionalInt`, etc.)
153
+
154
+ ### B) `apps/full-app` (integration app)
155
+
156
+ **Keep in app:**
157
+
158
+ - composition UI (`src/front/main.tsx`) and app branding
159
+ - deployment/runtime entry wiring
160
+
161
+ **Extract to `@boring/core/server` helpers:**
162
+
163
+ - auth proxy forwarding pattern from `src/server/main.ts`
164
+ - SPA static fallback + safe path checks
165
+ - CSP nonce HTML injector utility
166
+ - mixed `/auth/*` API-vs-frontend routing pattern
167
+
168
+ ### C) `apps/workspace-playground` (demo app)
169
+
170
+ **Keep in app:**
171
+
172
+ - showcase seed data/messages/session stories
173
+
174
+ **Move/replace with package utilities:**
175
+
176
+ - dedupe `apps/workspace-playground/src/mockApi.ts` against
177
+ `packages/workspace/src/testing/mockApi.ts`
178
+ - avoid growing app-local infra for fake FS/api when package testing utility
179
+ already exists
180
+
181
+ ### D) `packages/workspace` + `packages/agent`
182
+
183
+ - own all generic UI bridge behavior + its e2e/spec harness
184
+ - own generic chat-shell/dock persistence behavior tests
185
+ - avoid depending on macro for regressions in generic surfaces
186
+
187
+ ## Migration plan
188
+
189
+ ### Phase 0 — Build the workspace `./server` and `./shared` entries (prerequisite)
190
+
191
+ `packages/workspace/package.json` declares five subpath exports:
192
+
193
+ ```jsonc
194
+ {
195
+ ".": "./dist/workspace.js", // main API (ChatCenteredShell, panes, hooks, …)
196
+ "./testing": "./dist/testing.js", // test helpers (TestWorkspaceProvider, mocks)
197
+ "./ui-shadcn": "./dist/ui-shadcn.js", // shadcn primitives (Button, Card, …)
198
+ "./shared": "./dist/shared.js", // UiBridge / UiCommand / UiState types
199
+ "./server": "./dist/server.js" // createWorkspaceAgentApp, uiRoutes, uiTools
200
+ }
201
+ ```
202
+
203
+ …but `vite.config.ts`'s `build.lib.entry` only emits **three** of them:
204
+ `workspace`, `testing`, `ui-shadcn`. The `./server` and `./shared`
205
+ entries are declared in the package.json but never produced — so any
206
+ consumer outside the monorepo (or any tool that bypasses Vite's path
207
+ aliases) gets a broken import.
208
+
209
+ This is why earlier work had to **inline `createWorkspaceAgentApp` into
210
+ the consuming app** (`boring-macro-v2/src/server/uiBridge.ts` is a
211
+ copy-paste of `packages/workspace/src/server/createWorkspaceAgentApp.ts`
212
+ + `uiTools.ts` + `uiRoutes.ts`). Phase 1's "import from
213
+ `@boring/workspace/server`" doesn't actually work today; it has to be
214
+ made to work first.
215
+
216
+ **Tasks:**
217
+
218
+ 1. Add `tsup` to `packages/workspace/devDependencies` (already in
219
+ `agent` and `core` — copy that pattern).
220
+ 2. Add a `tsup.config.ts` that emits `dist/server.js` + `dist/server.d.ts`
221
+ from `src/server/index.ts`, and `dist/shared.js` + `dist/shared.d.ts`
222
+ from `src/shared/index.ts`.
223
+ 3. Update `package.json` `build` script:
224
+ `"build": "tsup && vite build"` (mirrors `agent`).
225
+ 4. Add `assert-build-artifacts.mjs` parity check (`agent` already has
226
+ one) so a missing entry fails the build instead of shipping a broken
227
+ package.
228
+ 5. Confirm `pnpm --filter @boring/workspace build` produces all five
229
+ entries declared in the exports map.
230
+ 6. Delete `boring-macro-v2/src/server/uiBridge.ts` and replace with
231
+ `import { createWorkspaceAgentApp } from "@boring/workspace/server"`.
232
+ This is the ground-truth verification that the build works.
233
+
234
+ Phase 0 is a prerequisite for Phase 1 (the fixture would otherwise have
235
+ to inline the same code), Phase 4 (extractions that target package
236
+ surfaces require the targets to actually be buildable), and the macro
237
+ consolidation work in
238
+ [`apps/boring-macro-v2/docs/CONSOLIDATE_AND_STANDALONIZE.md`](../../../apps/boring-macro-v2/docs/CONSOLIDATE_AND_STANDALONIZE.md)
239
+ (its Phase C drops the inlined `uiBridge.ts` workaround).
240
+
241
+ Estimated cost: half a day. Adds zero behavioural change — just makes
242
+ the package's declared API match what's shipped.
243
+
244
+ ### Phase 1 — Set up the workspace fixture (one PR)
245
+
246
+ 1. `packages/workspace/e2e/fixture/{index.html, main.tsx, App.tsx}` — minimal
247
+ ChatCenteredShell with a tiny mock series adapter and 2-3 panel registrations
248
+ (`code-editor`, `markdown-editor`, plus a stub `chart-canvas` that renders a
249
+ recharts `LineChart` against synthetic data so dockview-shrink tests have
250
+ something to clip).
251
+ 2. `packages/workspace/e2e/playwright.config.ts` — webServer boots Vite on
252
+ `localhost:5300` (sibling to whatever else may be running).
253
+ 3. `packages/workspace/e2e/helpers/{boot.ts, pane.ts}` — port `bootClean` and
254
+ the `openPaneViaBridge` waiter from `boring-macro-v2/e2e/helpers.ts`,
255
+ parameterized on storage key.
256
+ 4. `pnpm --filter @boring/workspace test:e2e` script.
257
+ 5. Add fixture-only deterministic adapters; do **not** reuse playground app code.
258
+
259
+ ### Phase 2 — Move generic specs from boring-macro
260
+
261
+ > **Coordination with the macro consolidation plan:** the 29 specs live in
262
+ > the *standalone* macro today. The macro plan's Phase B copies them into
263
+ > `apps/boring-macro-v2/e2e/`. Sequencing: do macro Phase B FIRST (move
264
+ > 29 specs in), then this Phase 2 (split — generic ones move to
265
+ > `packages/workspace/e2e/`, leaving ~14 macro-only). This avoids
266
+ > retroactively touching standalone files we're about to archive.
267
+
268
+ Specs that come over (one per file, lightly adapted to the fixture's storage key
269
+ and pane registry):
270
+
271
+ - `composer-border.spec.ts` — direct copy.
272
+ - `topbar.spec.ts` → `chat-shell-topbar.spec.ts` — assert `appTitle` prop is
273
+ rendered. Adapted from `"boring.macro"` to `"Workspace"` (whatever the
274
+ fixture sets).
275
+ - `split-no-clip.spec.ts` → `dock-split-shrink.spec.ts` — open two panes, drag
276
+ bottom split, measure that the recharts wrapper inside the second group
277
+ doesn't overflow. Uses the fixture's stub chart pane (no FRED).
278
+ - `layout-persistence.spec.ts` — sidebar collapsed/width persistence under
279
+ `<storageKey>:surface:*`, drawer/surface flags under `<storageKey>:*`.
280
+ - `agent.spec.ts` (the workspace half) → `bridge-openpanel.spec.ts` /
281
+ `bridge-no-surface-noop.spec.ts` — `POST /api/v1/ui/commands` accepts
282
+ `openPanel`, dispatcher noop's when no surface mounted, dispatch fires when it
283
+ does.
284
+
285
+ After Phase 2, `apps/boring-macro-v2/e2e/` shrinks from 29 → ~14 specs:
286
+ - `chat-suggestions.spec.ts` (macro labels)
287
+ - `catalog.spec.ts` (FRED catalog UI + 87k count + macro routes)
288
+ - `catalog-to-chart.spec.ts` (macro adapter onActivate → chart-canvas)
289
+ - `chart-tabs.spec.ts` (ChartCanvasPane Chart/Table/Metadata/Lineage + macro routes)
290
+ - `deck.spec.ts` (DeckPane + `/api/macro/deck/*`)
291
+ - `agent.spec.ts` (trimmed to assert macro tool catalog: `execute_sql`,
292
+ `macro_search`, `get_series_data`, `persist_derived_series`)
293
+
294
+ ### Phase 3 — Demo/app boundary cleanup (parallel with e2e migration)
295
+
296
+ 1. Replace playground-local FS mock backend with `@boring/workspace/testing`
297
+ equivalent (or extract common shared helper then consume from both).
298
+ 2. Keep playground focused on readable composition examples; move test harness
299
+ glue into `packages/workspace/e2e/fixture`.
300
+ 3. Add boundary guardrails:
301
+ - app-level domain terms (e.g. macro billing/waitlist/FRED policy) never land
302
+ in package runtime paths;
303
+ - generic dock/chat/bridge behaviors never rely on macro app tests.
304
+
305
+ ### Phase 4 — Shared helper extraction from apps
306
+
307
+ > **Gating:** `@boring/core` is currently WIP — its public surface isn't
308
+ > frozen. Don't promote into a moving target. Until core lands, the
309
+ > reusable bits below live in `apps/full-app/src/server/_shared/` (or
310
+ > equivalent app-local location) and migrate to `@boring/core/server` in
311
+ > a single follow-up PR once core's API stabilises. The review checklist
312
+ > in **Enforcement** below should reject premature `@boring/core/server`
313
+ > promotion until that gate clears.
314
+
315
+ 1. Stage reusable full-app server glue (auth proxy, static SPA fallback,
316
+ CSP nonce HTML transforms) in `apps/full-app/src/server/_shared/`.
317
+ Promote to `@boring/core/server` once core stabilises.
318
+ 2. Replace macro `tabBus.ts` with `bridge.postCommand({kind:"openPanel", ...})`
319
+ call sites — no extraction needed; the workspace UI bridge already
320
+ owns this surface.
321
+ 3. Extract generic queue/rate-limit primitive used by macro refresh
322
+ workflows into a package utility (provider-specific fetchers remain
323
+ app-owned). Initial home: `apps/boring-macro-v2/src/server/_shared/`,
324
+ promote later.
325
+
326
+ ### Phase 5 — New tests that don't have a home today
327
+
328
+ Things we want to lock down but that don't fit any current spec:
329
+
330
+ - `extra-panels.spec.ts` — passing `extraPanels={["foo"]}` on
331
+ ChatCenteredShell allows `surface.openPanel({component:"foo"})`; omitting it
332
+ blocks. Currently silent.
333
+ - `multi-shell.spec.ts` — two ChatCenteredShells on the same page with distinct
334
+ `storageKey` props don't trample each other's persisted state. Smoke-level
335
+ guard for future multi-pane embeds.
336
+ - `keyboard-shortcuts.spec.ts` — Cmd+1, Cmd+2, Esc behaviour. Touchy in
337
+ headless; documented separately so it can be skipped on CI matrices that
338
+ have known-flaky keyboards.
339
+
340
+ Phase 5 is opportunistic — none of it blocks Phases 1–4.
341
+
342
+ ## Enforcement / guardrails
343
+
344
+ - Add a lightweight ownership checklist to PR template:
345
+ - "Is this reusable across >=2 apps?" → package.
346
+ - "Is this product policy/brand/domain?" → app.
347
+ - Add CI boundary lint/audit tasks (initially warning, later fail):
348
+ - detect app-specific domain modules imported into package runtime code
349
+ - detect duplicated mock-api implementations when package testing helpers exist
350
+ - Keep `apps/workspace-playground` success metric: tiny, readable demo codebase.
351
+
352
+ ## Non-goals
353
+
354
+ - **Not** moving `boring-macro-v2`'s 14 macro-only specs upstream. They depend
355
+ on the macro Fastify routes + ClickHouse + the macro app's panes. Putting
356
+ them in workspace would force workspace to either mock those (unfaithful) or
357
+ spawn macro (heavy + wrong scope).
358
+ - **Not** generating a "second playground" for tests. The fixture app is a
359
+ test artifact, deliberately ugly and stable; the playground is a
360
+ documentation artifact, deliberately readable and idiomatic.
361
+ - **Not** unifying with `boring-ui-v2/e2e/`'s spawnBackend fixtures yet. That
362
+ suite is mature for backend testing; bolting browser specs onto it adds risk
363
+ without removing duplication. Revisit only if the helpers genuinely converge.
364
+
365
+ ## Deliverables
366
+
367
+ 1. `packages/workspace/e2e/fixture/**` + migrated generic specs.
368
+ 2. Reduced `apps/boring-macro-v2/e2e/**` containing only macro-specific specs.
369
+ 3. Playground mock backend deduplicated with workspace testing utility.
370
+ 4. Follow-up extraction beads:
371
+ - core server helpers from full-app
372
+ - delete macro `tabBus`; route via existing UI bridge
373
+ - generic queue/rate-limit primitive from macro refresh flow
374
+ 5. Boundary lint/checklist documented and wired into CI.
375
+
376
+ ## Open questions
377
+
378
+ - Does the fixture need its own `@boring/agent` backend (for the bridge specs),
379
+ or can the bridge run in-process inside the Vite plugin? Probably the
380
+ former, mirroring how the playground already boots `createAgentApp` from
381
+ vite.config.ts.
382
+ - Should the fixture export its mock adapter from `@boring/workspace/testing`
383
+ so child apps can reuse it? Tempting. Risk is that "testing" subpath grows
384
+ into another public surface to maintain. Decide after the fixture stabilises.
385
+ - CI cost: the fixture suite + macro suite + root e2e all boot Vite. Total
386
+ cold-boot is ~3 minutes of Vite work. If that's too much, share a
387
+ `vite-node-server` process across suites. Out of scope for the initial move.