@blackbelt-technology/pi-agent-dashboard 0.2.9 → 0.3.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 (76) hide show
  1. package/AGENTS.md +114 -9
  2. package/README.md +218 -97
  3. package/docs/architecture.md +107 -7
  4. package/package.json +9 -4
  5. package/packages/extension/package.json +1 -1
  6. package/packages/extension/src/__tests__/ask-user-tool.test.ts +300 -3
  7. package/packages/extension/src/__tests__/fork-entryid-timing.test.ts +100 -0
  8. package/packages/extension/src/ask-user-tool.ts +289 -20
  9. package/packages/extension/src/bridge.ts +38 -4
  10. package/packages/extension/src/command-handler.ts +34 -39
  11. package/packages/extension/src/prompt-expander.ts +25 -4
  12. package/packages/server/package.json +2 -1
  13. package/packages/server/src/__tests__/auto-attach.test.ts +10 -1
  14. package/packages/server/src/__tests__/auto-shutdown.test.ts +8 -2
  15. package/packages/server/src/__tests__/browse-endpoint.test.ts +229 -10
  16. package/packages/server/src/__tests__/browser-gateway-handler-errors.test.ts +129 -0
  17. package/packages/server/src/__tests__/cors.test.ts +34 -2
  18. package/packages/server/src/__tests__/editor-manager-pid-registry.test.ts +168 -0
  19. package/packages/server/src/__tests__/editor-manager.test.ts +33 -0
  20. package/packages/server/src/__tests__/editor-pid-registry.test.ts +191 -0
  21. package/packages/server/src/__tests__/editor-registry.test.ts +3 -2
  22. package/packages/server/src/__tests__/fix-pty-permissions.test.ts +59 -0
  23. package/packages/server/src/__tests__/git-operations.test.ts +9 -7
  24. package/packages/server/src/__tests__/health-endpoint.test.ts +11 -13
  25. package/packages/server/src/__tests__/openspec-tasks-parser.test.ts +178 -0
  26. package/packages/server/src/__tests__/openspec-tasks-routes.test.ts +180 -0
  27. package/packages/server/src/__tests__/package-manager-wrapper-resolve.test.ts +122 -0
  28. package/packages/server/src/__tests__/pi-core-checker.test.ts +195 -0
  29. package/packages/server/src/__tests__/pi-core-routes.test.ts +184 -0
  30. package/packages/server/src/__tests__/pi-core-updater.test.ts +214 -0
  31. package/packages/server/src/__tests__/provider-auth-routes.test.ts +13 -3
  32. package/packages/server/src/__tests__/recommended-routes.test.ts +389 -0
  33. package/packages/server/src/__tests__/session-file-dedup.test.ts +10 -10
  34. package/packages/server/src/__tests__/session-lifecycle-logging.test.ts +8 -2
  35. package/packages/server/src/__tests__/sleep-aware-heartbeat.test.ts +3 -1
  36. package/packages/server/src/__tests__/smoke-integration.test.ts +10 -10
  37. package/packages/server/src/__tests__/test-server-canary.test.ts +31 -0
  38. package/packages/server/src/__tests__/tunnel.test.ts +91 -0
  39. package/packages/server/src/__tests__/ws-ping-pong.test.ts +10 -2
  40. package/packages/server/src/browse.ts +100 -6
  41. package/packages/server/src/browser-gateway.ts +16 -3
  42. package/packages/server/src/editor-manager.ts +20 -1
  43. package/packages/server/src/editor-pid-registry.ts +198 -0
  44. package/packages/server/src/fix-pty-permissions.ts +44 -0
  45. package/packages/server/src/headless-pid-registry.ts +9 -0
  46. package/packages/server/src/npm-search-proxy.ts +71 -0
  47. package/packages/server/src/openspec-tasks.ts +158 -0
  48. package/packages/server/src/package-manager-wrapper.ts +31 -0
  49. package/packages/server/src/pi-core-checker.ts +290 -0
  50. package/packages/server/src/pi-core-updater.ts +166 -0
  51. package/packages/server/src/pi-gateway.ts +7 -0
  52. package/packages/server/src/process-manager.ts +1 -1
  53. package/packages/server/src/routes/file-routes.ts +30 -3
  54. package/packages/server/src/routes/openspec-routes.ts +83 -1
  55. package/packages/server/src/routes/pi-core-routes.ts +117 -0
  56. package/packages/server/src/routes/provider-auth-routes.ts +4 -2
  57. package/packages/server/src/routes/provider-routes.ts +12 -2
  58. package/packages/server/src/routes/recommended-routes.ts +227 -0
  59. package/packages/server/src/routes/system-routes.ts +10 -1
  60. package/packages/server/src/server.ts +151 -15
  61. package/packages/server/src/terminal-manager.ts +4 -0
  62. package/packages/server/src/test-env-guard.ts +26 -0
  63. package/packages/server/src/test-support/test-server.ts +63 -0
  64. package/packages/server/src/tunnel.ts +132 -8
  65. package/packages/shared/package.json +1 -1
  66. package/packages/shared/src/__tests__/config.test.ts +3 -3
  67. package/packages/shared/src/__tests__/openspec-poller.test.ts +44 -0
  68. package/packages/shared/src/__tests__/recommended-extensions.test.ts +123 -0
  69. package/packages/shared/src/__tests__/source-matching.test.ts +143 -0
  70. package/packages/shared/src/browser-protocol.ts +23 -1
  71. package/packages/shared/src/openspec-poller.ts +8 -3
  72. package/packages/shared/src/recommended-extensions.ts +180 -0
  73. package/packages/shared/src/rest-api.ts +71 -0
  74. package/packages/shared/src/source-matching.ts +126 -0
  75. package/packages/shared/src/test-support/setup-home.ts +74 -0
  76. package/packages/shared/src/types.ts +7 -0
package/AGENTS.md CHANGED
@@ -18,8 +18,8 @@ See [docs/architecture.md](docs/architecture.md) for full details.
18
18
 
19
19
  ```bash
20
20
  npm install # Install dependencies
21
- npm test # Run all tests (vitest)
22
- npm run test:watch # Watch mode
21
+ npm test # Run all tests (vitest) — ISOLATED HOME, safe for live sessions
22
+ npm run test:watch # Watch mode — also isolated
23
23
  npm run build # Build web client (Vite)
24
24
  npm run dev # Start Vite dev server
25
25
  npm run reload # Reload all connected pi sessions
@@ -28,6 +28,69 @@ pi-dashboard # Start dashboard server
28
28
  pi-dashboard --dev # Start with Vite proxy
29
29
  ```
30
30
 
31
+ ## Test Isolation (READ BEFORE RUNNING VITEST)
32
+
33
+ **Always run tests via `npm test` or `npm run test:watch`.** Never run `npx vitest` / `vitest` directly without also setting `HOME` to an ephemeral directory first.
34
+
35
+ ### Why this matters
36
+
37
+ Integration tests call `createServer()`, which on startup runs destructive sweeps against `$HOME/.pi/dashboard/`:
38
+ - `headlessPidRegistry.cleanupOrphans()` — reads `headless-pids.json` and may SIGTERM PIDs it considers orphans (including the **live pi process running the bridge**).
39
+ - `headlessPidRegistry.killAll()` — SIGTERMs every tracked PID on server shutdown.
40
+ - `editorPidRegistry.cleanupOrphans()` — SIGTERMs orphan code-server processes.
41
+
42
+ If tests run against your real `$HOME`, these sweeps can **kill the pi session that launched the tests**. They also write `.meta.json` sidecars into `~/.pi/agent/sessions/`, racing live bridges and corrupting real session state.
43
+
44
+ ### How isolation works
45
+
46
+ Three independent layers (defense-in-depth):
47
+
48
+ 1. **Process-level HOME override** (`package.json` `test` / `test:watch` scripts): `HOME=$(mktemp -d -t pi-test-XXXXXX) vitest ...` — vitest starts with a fresh HOME under `os.tmpdir()`.
49
+ 2. **globalSetup tripwire** (`packages/shared/src/test-support/setup-home.ts`, wired via `globalSetup` in every `vitest.config.ts`): runs ONCE at vitest boot, throws if `process.env.HOME === os.userInfo().homedir` — aborts the run before any test file loads. Pre-creates `.pi/agent/sessions/` and `.pi/dashboard/` subdirs.
50
+ 3. **Production-code guards** (`packages/server/src/test-env-guard.ts`): `headlessPidRegistry.cleanupOrphans`/`killAll` and `editorPidRegistry.cleanupOrphans` no-op with `console.warn` when `process.env.VITEST === "true"` AND HOME matches the real user home.
51
+
52
+ ### Running tests safely
53
+
54
+ ```bash
55
+ npm test # Preferred — all isolation layers active
56
+ npm run test:watch # Also safe
57
+ ```
58
+
59
+ If you must invoke vitest directly (e.g. for a single file):
60
+
61
+ ```bash
62
+ HOME=$(mktemp -d -t pi-test-XXXXXX) npx vitest run path/to/test.ts
63
+ ```
64
+
65
+ Without the `HOME=$(mktemp -d)` prefix, the globalSetup tripwire will abort the run with an instructive error — but **don't rely on that alone**: if you stash or delete the tripwire code, the prefix is your only remaining safety net.
66
+
67
+ ### Writing integration tests
68
+
69
+ Tests that boot a real `DashboardServer` SHOULD use the `createTestServer()` helper:
70
+
71
+ ```ts
72
+ import { createTestServer } from "@blackbelt-technology/pi-dashboard-server/test-support/test-server.js";
73
+
74
+ const { server, httpPort, piPort, stop } = await createTestServer();
75
+ // httpPort and piPort are OS-assigned (port: 0) — no hard-coded port ranges,
76
+ // no risk of colliding with the live dashboard on :8000 / :9999.
77
+ // ...
78
+ await stop();
79
+ ```
80
+
81
+ Avoid hard-coding ports like `19070`, `19080`, `19090`, etc. in new tests — the helper handles port allocation and safe defaults (`autoShutdown: false`, `tunnel: false`).
82
+
83
+ ### Verifying isolation after test changes
84
+
85
+ ```bash
86
+ find ~/.pi/agent/sessions -name "*.meta.json" -exec md5 -q {} \; | sort > /tmp/before.txt
87
+ npm test
88
+ find ~/.pi/agent/sessions -name "*.meta.json" -exec md5 -q {} \; | sort > /tmp/after.txt
89
+ diff /tmp/before.txt /tmp/after.txt | grep -v "pi-agent-dashboard" # should be empty
90
+ ```
91
+
92
+ Files under the **current** session's directory (`~/.pi/agent/sessions/--Users-...-pi-agent-dashboard--/`) will legitimately change during the test run because the live bridge keeps writing heartbeats. Only files in **other** session directories indicate a leak.
93
+
31
94
  ## Cross-Platform QA Testing
32
95
 
33
96
  VM-based QA testing for verifying clean-state installation and runtime across platforms.
@@ -86,7 +149,7 @@ make clean # Destroy all cloned VMs
86
149
  | `src/extension/prompt-bus.ts` | PromptBus — unified prompt routing to registered adapters (TUI, dashboard, custom). First-response-wins, cross-adapter dismissal. |
87
150
  | `src/extension/dashboard-default-adapter.ts` | Built-in PromptBus adapter that renders prompts as generic interactive dialogs in dashboard chat |
88
151
  | `src/client/lib/prompt-component-registry.ts` | Client-side component registry mapping prompt type strings to render metadata (placement, component) |
89
- | `src/extension/ask-user-tool.ts` | `ask_user` tool registration (bundled in bridge, registered at session_start to avoid static tool-name conflicts with other extensions) |
152
+ | `src/extension/ask-user-tool.ts` | `ask_user` tool registration (bundled in bridge, registered at session_start to avoid static tool-name conflicts with other extensions). Supports five methods: `confirm`, `select`, `multiselect`, `input`, and `batch` (multiple related questions in one call, answers returned as ordered array; cancel mid-batch returns partial results + `cancelled: true`). `prepareArguments` rescues common LLM drift: stringified `params`/`questions`/`options`, `question`/`header` → `title`, `input_type` wrapper flattening, `{label,value}[]` → `label[]` with warning surfaced via `details.warnings`. |
90
153
  | `src/shared/openspec-activity-detector.ts` | Detects OpenSpec activity from tool events; auto-attach requires only changeName (phase optional) |
91
154
  | `src/shared/openspec-poller.ts` | OpenSpec CLI polling (shared, used by server DirectoryService) |
92
155
  | `src/shared/state-replay.ts` | Synthesizes events from pi entries (shared, used by server + bridge) |
@@ -102,7 +165,7 @@ make clean # Destroy all cloned VMs
102
165
  | `src/server/idle-timer.ts` | Auto-shutdown idle timer with sleep-wake resilience |
103
166
  | `src/server/session-bootstrap.ts` | Startup session discovery and OpenSpec polling init |
104
167
  | `src/server/pi-gateway.ts` | Extension WebSocket gateway (port 9999) |
105
- | `src/server/browser-gateway.ts` | Browser WebSocket gateway (dispatches to handler modules) |
168
+ | `src/server/browser-gateway.ts` | Browser WebSocket gateway (dispatches to handler modules; logs handler exceptions as `[browser-gw] handler error type=<msg.type>: <err>` instead of silently swallowing them — only malformed JSON frames are dropped silently) |
106
169
  | `src/server/browser-handlers/handler-context.ts` | Shared context type for browser message handlers |
107
170
  | `src/server/browser-handlers/subscription-handler.ts` | Subscribe/unsubscribe with async batched replay, backpressure, lazy loading |
108
171
  | `src/server/browser-handlers/session-action-handler.ts` | Send prompt, abort, resume, spawn, shutdown, force kill, flow control |
@@ -118,16 +181,28 @@ make clean # Destroy all cloned VMs
118
181
  | `src/client/components/ArchiveBrowserView.tsx` | Searchable archive browser: date-grouped list, two-level nav to artifact reader |
119
182
  | `src/client/hooks/useArchiveListing.ts` | Fetch hook + pure helpers (groupByDate, filterEntries) for archive endpoint |
120
183
  | `src/server/openspec-archive.ts` | Scans `openspec/changes/archive/` and returns structured ArchiveEntry list |
184
+ | `packages/server/src/openspec-tasks.ts` | Parses an OpenSpec change's `tasks.md` (top-level `- [ ]` / `- [x]` lines under `## ` headings) and rewrites a single checkbox line atomically (tmp + rename, byte-for-byte preservation of other lines). Exports `parseTasksMarkdown`, `readTasks`, `toggleTask`, plus typed `NotFoundError` / `LineMismatchError` / `NotACheckboxError` consumed by `routes/openspec-routes.ts` to map to HTTP 404/409/400. |
185
+ | `packages/client/src/lib/openspec-tasks-api.ts` | Client fetch helpers for `GET /api/openspec/tasks` and `POST /api/openspec/tasks/toggle`. Throws typed `LineMismatchError` on HTTP 409 so `TasksPopover` can refetch + show a banner without string-matching error text. |
186
+ | `packages/client/src/components/StatePill.tsx` | Compact color-coded pill (zinc/blue/amber/green) showing `deriveChangeState(change)` next to the attached-change badge on the session card. Exports `stateToLabel` and the `STATE_PILL_CLASS` map for reuse. |
187
+ | `packages/client/src/components/TasksPopover.tsx` | Portal-rendered popover (anchored to the session card's `Tasks N/M` button) listing every parseable `tasks.md` checkbox grouped by `## ` heading. Optimistic toggle; HTTP 409 triggers a refetch + “File changed” banner; broadcast-driven openspec poll keeps the card counts fresh after each tick.
121
188
  | `src/client/components/SessionOpenSpecActions.tsx` | Session-level OpenSpec: searchable attach dialog, action buttons, detach |
122
189
  | `src/client/components/DialogPortal.tsx` | Portal wrapper rendering dialogs at document.body with scroll lock |
123
- | `src/client/components/PinDirectoryDialog.tsx` | Dialog to pin a directory (wraps PathPicker) |
190
+ | `src/client/components/PinDirectoryDialog.tsx` | Dialog to pin a directory (wraps PathPicker). Mounted once at the app root in `App.tsx` via `DialogPortal`; opened by both the sidebar "Add folder" button and the LandingPage Step ② CTA through a shared `onOpenPinDialog` callback. |
191
+ | `packages/client/src/components/LandingPage.tsx` | Empty-state onboarding view. Renders three cards (Setup credentials → Add folder → Start session) that collapse to ✔ rows once each step is satisfied. Accepts `{ providersReady, pinnedCount, sessionsCount, firstPinnedCwd, onOpenPinDialog, onSpawnSession, navigate }`. Falls back to the legacy `π + "Select a session to get started"` placeholder when no onboarding props are supplied (preserves existing test surface). |
192
+ | `packages/client/src/hooks/useProvidersReady.ts` | Hook observing `GET /api/providers`: returns `{ loading, ready, count }` where `ready=true` iff at least one provider has a non-empty `apiKey`. Refetches on window `focus` and on `provider-auth-event` custom events. Used by `LandingPage` via `App.tsx` to drive Step ① state. |
124
193
  | `src/client/components/PathPicker.tsx` | Reusable keyboard-first path picker with typeahead directory list |
125
194
  | `src/client/lib/browse-api.ts` | Client-side browse API helper for PathPicker |
126
195
  | `src/server/browse.ts` | Directory listing logic for browse API endpoint |
127
196
  | `src/server/pi-resource-scanner.ts` | Discovers pi extensions, skills, prompts from local, global, and package sources |
128
- | `src/server/package-manager-wrapper.ts` | Thin adapter around pi's `DefaultPackageManager` with operation serialization, progress forwarding, and session reload |
197
+ | `src/server/package-manager-wrapper.ts` | Thin adapter around pi's `DefaultPackageManager` with operation serialization, progress forwarding, and session reload. `loadPiPackageManager()` resolution order: (1) direct import of `@mariozechner/pi-coding-agent`, (2) managed install at `~/.pi-dashboard/node_modules/` (used by Electron portable/standalone on Windows where pi isn't globally installed), (3) `npm root -g`. Without step (2) portable Windows users see a red "pi-coding-agent is not installed" banner in the Packages tab. |
129
198
  | `src/server/npm-search-proxy.ts` | Cached proxy for npm registry search (`keywords:pi-package`) and README fetch |
130
199
  | `src/server/routes/package-routes.ts` | REST routes: search, readme, installed, install, remove, update, check-updates |
200
+ | `packages/server/src/pi-core-checker.ts` | Discovers pi ecosystem CORE CLI packages (pi itself, pi-dashboard, pi-model-proxy, etc.) from global npm + `~/.pi-dashboard/node_modules/` and compares against the npm registry. Complements PackageManagerWrapper which only manages extensions in `settings.json packages[]`. 5-min cache. |
201
+ | `packages/server/src/pi-core-updater.ts` | Runs `npm update -g <pkg>` (global install) or `npm update <pkg>` in `~/.pi-dashboard/` (managed install), streams progress via listener, acquires `PackageManagerWrapper.runExclusive()` busy-lock, auto-reloads sessions on any successful update. |
202
+ | `packages/server/src/routes/pi-core-routes.ts` | REST routes: `GET /api/pi-core/versions[?refresh=true]`, `POST /api/pi-core/update` with `{ packages?: string[] }` (empty = update all with `updateAvailable`). 409 when busy, 400 on unknown package names. |
203
+ | `packages/client/src/hooks/usePiCoreVersions.ts` | Fetch + 30-min poll hook for pi core version status. Refetches (with `?refresh=true`) when `pi_core_update_complete` arrives via the `pi-core-event` window event. |
204
+ | `packages/client/src/components/PiCoreVersionsSection.tsx` | Settings → Packages tab section showing core packages with current → latest versions, per-package Update button, Update All, Check Now, live progress/error surfaced from WS `pi_core_update_progress`/`complete`. |
205
+ | `packages/client/src/components/PiUpdateBadge.tsx` | Header badge (count of available pi core updates). Hidden when zero. Clicking navigates to `/settings?tab=packages`. Mounted next to `ServerSelector` in `App.tsx` `headerExtra`. |
131
206
  | `src/client/components/SortablePinnedGroup.tsx` | Drag-to-reorder wrapper for pinned directory groups |
132
207
  | `src/server/preferences-store.ts` | Global UI preferences (pinned dirs, session order) in `preferences.json` |
133
208
  | `src/server/meta-persistence.ts` | Per-session debounced `.meta.json` writer |
@@ -141,6 +216,7 @@ make clean # Destroy all cloned VMs
141
216
  | `src/server/process-manager.ts` | Session spawning via tmux or headless mode |
142
217
  | `src/server/editor-registry.ts` | Detects available native editors (running processes + CLI) |
143
218
  | `src/server/editor-manager.ts` | Lifecycle manager for code-server child processes (spawn, stop, idle, heartbeat) |
219
+ | `packages/server/src/editor-pid-registry.ts` | Persists spawned code-server PIDs to `~/.pi/dashboard/editor-pids.json` and sweeps orphaned code-server processes on server boot (runs in `server.start()` before `fastify.listen`). Verifies ownership via cmdline check against `~/.pi/dashboard/editors/` prefix to avoid killing unrelated user-run code-server instances. SIGTERM → 1s grace → SIGKILL escalation. |
144
220
  | `src/server/editor-proxy.ts` | Reverse proxy for `/editor/:id/*` to code-server instances |
145
221
  | `src/server/editor-detection.ts` | Auto-detect code-server/openvscode-server binary on PATH |
146
222
  | `src/server/routes/editor-routes.ts` | REST routes: editor start, stop, heartbeat, status, detect |
@@ -164,8 +240,12 @@ make clean # Destroy all cloned VMs
164
240
  | `src/server/routes/known-servers-routes.ts` | REST routes: known servers CRUD, on-demand mDNS discovery scan |
165
241
  | `src/server/terminal-manager.ts` | PTY lifecycle, ring buffer, spawn/attach/kill terminals |
166
242
  | `src/server/terminal-gateway.ts` | Binary WebSocket upgrade handler for `/ws/terminal/:id` |
167
- | `scripts/fix-pty-permissions.cjs` | Postinstall: fix node-pty spawn-helper execute permissions |
168
- | `src/server/tunnel.ts` | Zrok tunnel with reserved shares for persistent URLs, binary detection, PID tracking, stale cleanup |
243
+ | `scripts/fix-pty-permissions.cjs` | Postinstall: locates `node-pty` via `require.resolve("node-pty/package.json")` (hoist-aware) and chmods every `prebuilds/*/spawn-helper` and `prebuilds/*/pty.node` to `0o755`. Wired up at the workspace-root `postinstall` so it applies regardless of which workspace triggered `npm install`. |
244
+ | `src/server/fix-pty-permissions.ts` | Runtime spawn-helper permission fix called once by `createTerminalManager()`, uses `createRequire().resolve("node-pty")` to handle any install layout including Electron bundles |
245
+ | `src/server/tunnel.ts` | Zrok tunnel with reserved shares for persistent URLs. Serialized creation via in-flight promise (`pendingCreate`) prevents parallel reservations from UI double-clicks. `releaseShare(token)` + `scavengeOrphanZrokProcesses(port)` clean up leaked reservations and processes on startup (unconditional when zrok binary is present, even in `--no-tunnel`), on `/api/restart`, `/api/shutdown`, and `/api/tunnel-disconnect`. Retry cap of 1 prevents cascade leaks; timeout path SIGTERM→SIGKILL escalation + release just-in-time tokens. |
246
+ | `packages/client/vite.config.ts` | Client build. `rollupOptions.output.manualChunks` splits bundle into vendor chunks (`react-vendor`, `markdown`, `syntax`, `diff`, `xterm`, `dnd`, `util`) so the initial chunk is ~570 KB (~150 KB gzipped) instead of a 3 MB monolith — avoids tunnel abort thresholds on large assets. |
247
+ | `packages/client/scripts/precompress.mjs` | Post-build step (runs from `build` / `prepare`): zero-dependency Node script that writes `.gz` siblings for every compressible file in `dist/`. `@fastify/static` serves them with `preCompressed: true` so responses ship a stable `Content-Length` header — avoids streaming-compression quirks in intermediate HTTP/2 proxies. |
248
+ | `packages/server/src/server.ts` | Fastify server. Registers `@fastify/compress` globally with gzip+deflate (brotli intentionally disabled — zrok free proxy stream-resets `content-encoding: br`). Serves static client with `preCompressed: true`. CORS callback allows localhost, the active zrok tunnel URL (via `getTunnelUrl()`), any `*.share.zrok.io` host, and configured origins; on mismatch returns `cb(null, false)` rather than throwing — throwing causes `@fastify/cors` to surface HTTP 500 on every asset response (the hidden root cause of browser-only 500s over zrok tunnels, since Vite's `<script type="module" crossorigin>` entries always request in CORS mode). |
169
249
  | `src/client/components/TunnelButton.tsx` | Unified tunnel/QR button — tunnel icon when not set up, QR icon when inactive, green QR icon when connected; opens QR dialog with disconnect/setup |
170
250
  | `src/client/components/QrCodeDialog.tsx` | QR code dialog showing tunnel URL as scannable QR code with copy, disconnect, and setup buttons |
171
251
  | `public/manifest.json` | PWA web app manifest for installability |
@@ -174,6 +254,11 @@ make clean # Destroy all cloned VMs
174
254
  | `src/server/cli.ts` | CLI entry point with subcommands (start/stop/restart/status) |
175
255
  | `src/shared/rest-api.ts` | REST API type definitions |
176
256
  | `scripts/reload-all.sh` | Build bridge + reload all pi sessions |
257
+ | `CHANGELOG.md` | Human-authored release notes per version (Keep a Changelog 1.1.0); source of GitHub Release bodies. Contributors append bullets to `## [Unreleased]`; release author promotes at tag time. |
258
+ | `packages/shared/src/test-support/setup-home.ts` | Vitest `globalSetup` module. Runs ONCE at vitest boot. Tripwire: throws if `process.env.HOME === os.userInfo().homedir` (i.e. tests would run against real user home), aborting the entire run before any test file loads. Warns if HOME is outside `os.tmpdir()`. Pre-creates `<HOME>/.pi/agent/sessions/` and `<HOME>/.pi/dashboard/`. Wired via `test.globalSetup` in every package's `vitest.config.ts`. |
259
+ | `packages/server/src/test-env-guard.ts` | `isUnsafeTestHomeScan()` — returns true when `VITEST=true` AND HOME matches `os.userInfo().homedir`. Gates destructive sweeps in `headlessPidRegistry.cleanupOrphans/killAll` and `editorPidRegistry.cleanupOrphans` so tests never SIGTERM live pi processes even if higher isolation layers are bypassed. |
260
+ | `packages/server/src/test-support/test-server.ts` | `createTestServer(overrides)` helper — boots a real `DashboardServer` on OS-assigned ports (`port: 0`, `piPort: 0`) with safe defaults (no auto-shutdown, no tunnel). Returns `{ server, httpPort, piPort, stop }`. Prefer over hard-coded `19xxx` port constants in new integration tests. |
261
+ | `docs/release-process.md` | Canonical how-to for cutting a release: commit conventions, Unreleased promotion, version bump, tag + push, CI behavior, manual fallback. |
177
262
  | `src/client/components/PiResourcesView.tsx` | Content area view for browsing pi extensions, skills, and prompts (with Installed/Packages tabs) |
178
263
  | `src/client/components/PackageBrowser.tsx` | Reusable inline package browser: npm search, type filters, install/uninstall, manual URL input |
179
264
  | `src/client/components/PackageCard.tsx` | Package card with type badges, downloads, install/uninstall actions |
@@ -181,6 +266,11 @@ make clean # Destroy all cloned VMs
181
266
  | `src/client/components/PackageInstallConfirmDialog.tsx` | Confirmation dialog before package install (name, source, scope) |
182
267
  | `src/client/hooks/usePackageSearch.ts` | Debounced fetch hook for `/api/packages/search` |
183
268
  | `src/client/hooks/useInstalledPackages.ts` | Fetch hook for `/api/packages/installed` |
269
+ | `src/client/hooks/useRecommendedExtensions.ts` | Fetch hook for `/api/packages/recommended`; auto-refreshes on `package_operation_complete` |
270
+ | `src/client/components/RecommendedExtensions.tsx` | Curated-recommended-extensions card grid, rendered above search in Packages tab |
271
+ | `src/client/components/MissingRequiredBanner.tsx` | Top-of-page banner when any `required` recommended extension is missing from `~/.pi/agent/settings.json` `packages[]` |
272
+ | `src/shared/recommended-extensions.ts` | Static `RECOMMENDED_EXTENSIONS` manifest (pi-anthropic-messages, @tintinweb/pi-subagents, pi-flows, pi-web-access, pi-agent-browser); enriched server-side with live description/version and installed/active cross-reference |
273
+ | `src/server/routes/recommended-routes.ts` | `GET /api/packages/recommended` — enrichment + 60s cache, invalidated on successful install/remove/update |
184
274
  | `src/client/hooks/usePackageOperations.ts` | Install/remove/update actions with WebSocket progress listening |
185
275
  | `src/client/hooks/usePiResources.ts` | Fetch + 30s polling hook for pi resources API |
186
276
  | `src/client/components/MarkdownPreviewView.tsx` | Generic reusable markdown preview with back button, tabs, loading/error states |
@@ -191,7 +281,9 @@ make clean # Destroy all cloned VMs
191
281
  | `src/client/components/TerminalsView.tsx` | Tabbed terminal container per folder (tab bar, keep-alive, rename) |
192
282
  | `src/client/components/EditorView.tsx` | code-server iframe embedding with lazy start and heartbeat |
193
283
  | `src/client/components/EditorInstallGuide.tsx` | Platform-specific code-server installation guide |
194
- | `src/client/components/FolderActionBar.tsx` | Unified action bar per folder: +Session, +Terminal, Terminals(N), Editor, Zed, Pi Resources |
284
+ | `src/client/components/FolderActionBar.tsx` | Unified action bar per folder: +Session, Terminals(N), Editor, Zed, Pi Resources |
285
+ | `packages/client/src/hooks/useImagePaste.ts` | Shared clipboard-image-paste hook (pendingImages, imageError, handlePaste, removeImage, clearImages). Used by `CommandInput` and `ExploreDialog` so behavior is identical across both prompt surfaces. Exports `MAX_IMAGE_SIZE` and `SUPPORTED_IMAGE_TYPES`. |
286
+ | `packages/client/src/components/ImagePreviewStrip.tsx` | Shared image preview strip: error banner + horizontal thumbnail row with remove buttons; thumbnails open `ImageLightbox`. Used by `CommandInput` and `ExploreDialog`. |
195
287
  | `src/client/lib/folder-encoding.ts` | Base64url encode/decode for folder paths in URL routes |
196
288
  | `src/shared/editor-types.ts` | Editor instance types shared across components |
197
289
  | `src/client/components/TerminalCard.tsx` | Sidebar card for terminal sessions (cyan accent) |
@@ -200,6 +292,11 @@ make clean # Destroy all cloned VMs
200
292
  | `src/client/components/MobileActionMenu.tsx` | Kebab menu for session actions on mobile (includes OpenSpec commands) |
201
293
  | `src/client/components/MobileOverlay.tsx` | Hamburger button and sidebar overlay for mobile |
202
294
  | `src/client/components/SessionHeader.tsx` | Session header with OpenSpec attach/detach, flow launcher, MobileAttachButton |
295
+ | `packages/client/src/components/PiLogo.tsx` | Inline-SVG Pi brand-mark component. `fill="currentColor"`, transparent background, themeable via parent `color`. Used in both sidebar headers (`SessionList.tsx`, `SessionSidebar.tsx`). |
296
+ | `packages/client/src/components/SessionSidebar.tsx` | Alternate sidebar; header brand button renders `<PiLogo size={24} />` (inherits `text-blue-500 hover:text-blue-400`) instead of a literal `π` glyph. |
297
+ | `packages/client/src/components/SessionList.tsx` | Desktop sidebar; header brand button uses `<PiLogo />`. Pin-folder button renders `📌 Add folder` label (tooltip: "Pin a folder to the sidebar") instead of icon-only `📌+`. |
298
+ | `packages/client/src/index.css` | Global styles. `.card-working-pulse` layers a 45° repeating-linear-gradient (amber, `background-size: 28.2843px` = one full diagonal period) over a flat amber tint; `background-position` scrolls **horizontally** by `56.5685px` (2 periods) over 2s linear — scroll must be across stripes, diagonal scroll is pattern-invariant. An opacity pulse `0.6 → 1 → 0.6` runs in parallel over 3s ease-in-out (coprime with 2s stripes so they never lock). `prefers-reduced-motion: reduce` disables both animations but keeps the static stripe pattern as a state cue. `.card-input-pulse` (ask_user) intentionally stays pulse-only — stripes = working, pulse-only = waiting on you. |
299
+ | `packages/client/vite.config.ts` | Client build config. `publicDir: "../../../public"` — resolved relative to `root: "src"`, three `../` hops reach the project-root `public/` directory (icon-192.png, icon-512.png, manifest.json, sw.js). Earlier `"../../public"` silently resolved to a non-existent `packages/public/` and caused all PWA assets to 404 in production. |
203
300
  | `src/client/hooks/useSwipeBack.ts` | iOS-style left-edge swipe-back gesture (40px edge zone, document-level listeners) |
204
301
  | `src/client/components/ChatView.tsx` | Chat message view with scroll-lock: pauses auto-scroll when user scrolls up, floating scroll-to-bottom button, per-session scroll position persistence |
205
302
  | `src/client/lib/mobile-depth.ts` | Pure function computing MobileShell depth from route state |
@@ -232,6 +329,8 @@ make clean # Destroy all cloned VMs
232
329
  | `src/client/lib/diff-tree.ts` | Directory tree builder from flat file paths |
233
330
  | `src/server/session-api.ts` | REST wrappers for WebSocket-only session operations (prompt, abort, spawn, resume, etc.) |
234
331
  | `.pi/skills/pi-dashboard/SKILL.md` | Bundled skill: monitor and control the dashboard from any pi session |
332
+ | `.pi/skills/release-cut/SKILL.md` | Skill: cut a new release — pre-flight (clean tree, tests, build), CHANGELOG `[Unreleased]` → versioned promotion, workspace version bump, `chore(release): v<version>` commit, tag + push develop. Stops at the CI-generated draft GitHub Release per `docs/release-process.md`. |
333
+ | `.pi/skills/release-revoke/SKILL.md` | Skill: revoke/rollback a release — delete GitHub Release (draft or published) via `gh release delete`, remove git tag locally + on origin, `npm deprecate` the version (never `npm unpublish` — blocked after 72h / with dependents), optionally revert the `chore(release): v<version>` commit. Handles partial CI failures gracefully. |
235
334
  | `.pi/skills/pi-dashboard/references/api-reference.md` | Complete REST API reference for the skill |
236
335
  | `.pi/skills/pi-dashboard/references/recipes.md` | Multi-step orchestration recipes |
237
336
  | `.pi/skills/pi-dashboard/scripts/dashboard-api.sh` | Helper script with port auto-detection and auth |
@@ -267,6 +366,12 @@ make clean # Destroy all cloned VMs
267
366
  | `packages/electron/scripts/test-electron-install-inner.sh` | Inner test script run inside Docker container |
268
367
  | `packages/electron/resources/icon.png` | Master 1024×1024 app icon (π on dark navy) |
269
368
  | `.github/workflows/publish.yml` | CI: builds DMG (macOS), DEB+AppImage (Linux), NSIS+ZIP+portable (Windows) on native runners; publishes npm + GitHub Release |
369
+ | `.github/workflows/deploy-site.yml` | CI: builds marketing site in `/site` with Astro, enforces 50 KB gzipped JS budget, deploys to GitHub Pages via `actions/deploy-pages@v4` on push to `develop` when `site/**` changes |
370
+ | `site/` | Public marketing site (Astro + Tailwind + MDX + Preact). Self-contained, published at `BlackBeltTechnology.github.io/pi-agent-dashboard` (later `pi-dashboard.dev`). Uses Pi-blue palette, Supabase-style playful bento grid, 4-state storytelling hero animation. |
371
+ | `site/src/components/HeroAnimation.tsx` | Preact island — the only hydrated hero component. Cycles through dashboard state screenshots every 6s with motion-one crossfade; pauses on hover/touch; respects `prefers-reduced-motion`. |
372
+ | `site/src/content/features.ts` | Data-driven feature list rendered by `BentoGrid.astro` into the 12-card bento layout. |
373
+ | `site/scripts/screenshots/capture.ts` | Playwright screenshot pipeline. Two modes: `SCREENSHOT_TARGET_URL=<url> npm run screenshots` captures against an existing dashboard (recommended); plain `npm run screenshots` spawns a temp `pi-dashboard` with fixture sessions, captures desktop (1440) + mobile (390), and cleans up. Outputs to `site/public/screenshots/{desktop,mobile}/`. |
374
+ | `site/scripts/check-js-size.mjs` | Enforces the 50 KB gzipped JavaScript budget on `site/dist/**/*.js` (wired to `npm run size` and to the deploy workflow). |
270
375
 
271
376
  ## Build & Restart Workflow
272
377