pmx-canvas 0.4.1 → 0.4.3

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 (37) hide show
  1. package/.github/extensions/pmx-canvas/extension.mjs +5 -2
  2. package/CHANGELOG.md +61 -0
  3. package/Readme.md +34 -12
  4. package/dist/canvas/global.css +406 -0
  5. package/dist/canvas/index.js +84 -84
  6. package/dist/canvas/surface-theme.css +160 -0
  7. package/dist/json-render/index.css +1 -1
  8. package/dist/types/client/state/iframe-mode.d.ts +1 -0
  9. package/dist/types/client/state/theme-override.d.ts +27 -0
  10. package/dist/types/client/types.d.ts +2 -1
  11. package/dist/types/server/canvas-db.d.ts +2 -1
  12. package/dist/types/server/html-surface.d.ts +8 -1
  13. package/dist/types/shared/themes.d.ts +27 -0
  14. package/docs/cli.md +1 -1
  15. package/docs/environment.md +2 -1
  16. package/docs/http-api.md +15 -0
  17. package/docs/plans/plan-009-tech-debt-backlog.md +5 -3
  18. package/docs/screenshot.png +0 -0
  19. package/package.json +3 -3
  20. package/skills/pmx-canvas/SKILL.md +19 -3
  21. package/skills/pmx-canvas/references/full-reference.md +3 -3
  22. package/src/cli/index.ts +3 -2
  23. package/src/client/App.tsx +208 -53
  24. package/src/client/canvas/CommandPalette.tsx +4 -0
  25. package/src/client/nodes/ExtAppFrame.tsx +18 -5
  26. package/src/client/nodes/McpAppNode.tsx +10 -2
  27. package/src/client/state/iframe-mode.ts +16 -1
  28. package/src/client/state/sse-bridge.ts +13 -4
  29. package/src/client/state/theme-override.ts +83 -0
  30. package/src/client/theme/global.css +406 -0
  31. package/src/client/theme/surface-theme.css +160 -0
  32. package/src/client/types.ts +3 -1
  33. package/src/server/canvas-db.ts +3 -2
  34. package/src/server/html-surface.ts +17 -3
  35. package/src/server/operations/ops/canvas-wire.ts +15 -2
  36. package/src/server/server.ts +35 -1
  37. package/src/shared/themes.ts +58 -0
@@ -252,7 +252,10 @@ function escapeHtml(value) {
252
252
 
253
253
  function renderShell(instanceId, entry) {
254
254
  const status = entry.pmx?.ok ? "Connected" : "Not connected";
255
- const frameSrc = entry.pmx?.baseUrl ? `${entry.pmx.baseUrl}/workbench` : "about:blank";
255
+ // theme=light is a client-local session default matching the Copilot app's
256
+ // light chrome — it never changes the server-global theme other clients
257
+ // see, and the in-panel theme picker still overrides it.
258
+ const frameSrc = entry.pmx?.baseUrl ? `${entry.pmx.baseUrl}/workbench?theme=light` : "about:blank";
256
259
  const error = entry.pmx?.error ? `<p class="error">${escapeHtml(entry.pmx.error)}</p>` : "";
257
260
  return `<!doctype html>
258
261
  <html>
@@ -731,7 +734,7 @@ const pmxCanvas = createCanvas({
731
734
  return {
732
735
  title: "PMX Canvas",
733
736
  status: "Connected",
734
- url: `${pmx.baseUrl}/workbench`,
737
+ url: `${pmx.baseUrl}/workbench?theme=light`,
735
738
  };
736
739
  }
737
740
 
package/CHANGELOG.md CHANGED
@@ -3,6 +3,65 @@
3
3
  All notable changes to `pmx-canvas` are documented here. This project follows
4
4
  [Semantic Versioning](https://semver.org/).
5
5
 
6
+ ## [0.4.3] - 2026-08-05
7
+
8
+ ### Added
9
+
10
+ - Host-default theming: open the workbench with `?theme=<name>` (or
11
+ `?theme=auto` to follow the host's light/dark appearance) and that panel
12
+ gets its own session-local theme without changing the server-global theme
13
+ other clients see. Picking a theme from the toolbar ends the override. The
14
+ bundled GitHub Copilot extension now opens its panel with `?theme=light`.
15
+
16
+ ### Changed
17
+
18
+ - The `ember` theme was retuned to its intended look: neutral charcoal
19
+ surfaces, warm off-white text, and a vivid orange-red accent.
20
+
21
+ ### Fixed
22
+
23
+ - The theme picker and More menus now open anchored to their buttons instead
24
+ of at the toolbar's right edge (0.4.2 report Finding T).
25
+ - `POST /api/canvas/theme` now rejects unknown theme names with a 400 error
26
+ instead of silently keeping the previous theme.
27
+ - An app node whose live session errors after a stressed reconnect (0.4.2
28
+ report Finding U, e.g. "MCP error -32001") now shows a soft
29
+ degraded-interactivity notice over its still-rendered saved content instead
30
+ of a red error banner.
31
+ - Graph, json-render, and app nodes no longer stay blank after a refresh in
32
+ srcdoc mode until scrolled or expanded — inline documents now load eagerly
33
+ (lazy loading only applies in `src` mode, where it defers a real HTTP
34
+ request).
35
+ - The npm package's `test` scripts now explain that the test suite is not
36
+ shipped instead of failing with a confusing "no test files" error.
37
+
38
+ ## [0.4.2] - 2026-08-04
39
+
40
+ ### Added
41
+
42
+ - Five new themes — `midnight`, `sepia`, `arctic`, `ember`, and `forest` —
43
+ alongside `dark`, `light`, and `high-contrast`, selectable from a new theme
44
+ picker in the toolbar, via `--theme=...`, or `PMX_CANVAS_THEME`.
45
+ - The toolbar now collapses on narrow screens and mobile into a single compact
46
+ bar with a "⋯" menu holding every secondary action as labeled touch-friendly
47
+ rows, replacing the wrapped two-bar layout.
48
+ - Amp orbs are auto-detected: when the server runs with `AMP_ORB` set, the
49
+ embedded canvas skips the (there-unreliable) iframe probe and uses `srcdoc`
50
+ mode directly.
51
+
52
+ ### Changed
53
+
54
+ - Refreshed the bundled skill's accuracy: tool tables verified against the
55
+ live 22-tool MCP surface, stale composite counts corrected, and the browser
56
+ guidance now documents the toolbar theme picker and the narrow-screen More
57
+ menu.
58
+
59
+ ### Fixed
60
+
61
+ - HTML surfaces now inline their theme stylesheet instead of linking it, so
62
+ srcdoc-rendered surfaces in nested-iframe hosts render styled instead of
63
+ blank (dark-on-dark).
64
+
6
65
  ## [0.4.1] - 2026-08-04
7
66
 
8
67
  ### Added
@@ -2853,6 +2912,8 @@ otherwise have to discover by trial and error.
2853
2912
  - Regression coverage for snapshot flat-`id` aliases on both MCP and
2854
2913
  HTTP surfaces, plus async / top-level-`await` WebView script bodies.
2855
2914
 
2915
+ [0.4.3]: https://github.com/pskoett/pmx-canvas/releases/tag/v0.4.3
2916
+ [0.4.2]: https://github.com/pskoett/pmx-canvas/releases/tag/v0.4.2
2856
2917
  [0.4.1]: https://github.com/pskoett/pmx-canvas/releases/tag/v0.4.1
2857
2918
  [0.4.0]: https://github.com/pskoett/pmx-canvas/releases/tag/v0.4.0
2858
2919
  [0.3.4]: https://github.com/pskoett/pmx-canvas/releases/tag/v0.3.4
package/Readme.md CHANGED
@@ -130,7 +130,8 @@ surfaces (the core never imports a host SDK).
130
130
 
131
131
  - **GitHub Copilot app** — a committed project canvas extension
132
132
  (`.github/extensions/pmx-canvas/`) opens the live PMX workbench in a native
133
- Copilot panel, injects pinned/focused AX context on prompt submission, and
133
+ Copilot panel (light-themed by default to match the app, via the `?theme=`
134
+ session override), injects pinned/focused AX context on prompt submission, and
134
135
  exposes actions for focus, session steering, work items, approval gates,
135
136
  review annotations, the AX timeline, and host-capability reporting. Install it
136
137
  into any repo with `pmx-canvas copilot install-extension` (`--dry-run` to
@@ -142,17 +143,25 @@ surfaces (the core never imports a host SDK).
142
143
  (MCP + in-app Browser) are exactly what the canvas requires.
143
144
  - **Claude Code desktop app** — the workbench runs in the desktop app's
144
145
  built-in browser pane alongside the PMX MCP server, same zero-adapter recipe
145
- as Codex: open `http://localhost:4313/workbench` in the app's browser and the
146
- live board, pins, and Ghost Cursor all work in-panel.
147
- - **Amp orb portals** (and other buffering proxies / nested-iframe embeds) —
148
- the canvas detects when the live event stream is being buffered by the proxy
149
- and automatically switches to a polling transport, so the board boots and
150
- stays live through the portal with no proxy shims
151
- (`/workbench?transport=poll` forces it). It also detects when the nested
152
- iframe embed blocks `src`-URL iframes entirely and renders HTML, graph,
153
- json-render, and app surfaces inline via `srcdoc` instead
154
- (`?iframe-mode=srcdoc` forces it), so iframe-backed nodes work in the portal
155
- too.
146
+ as Codex: open `http://localhost:4313/workbench?theme=light` (or
147
+ `?theme=auto` to follow the app's appearance) in the app's browser and the
148
+ live board, pins, and Ghost Cursor all work in-panel — themed to match the
149
+ pane without changing what other clients see.
150
+ - **AMPCode orbs + portal** — run the canvas as an orb service and view the
151
+ live workbench through the Amp portal (the `*.onamp.dev` URL ampcode.com
152
+ embeds for the thread). Zero configuration: orbs set `AMP_ORB` in the
153
+ service environment, and the canvas auto-adapts to the portal's constraints
154
+ — it switches to a polling transport when the portal proxy buffers the live
155
+ event stream, and renders HTML, graph, json-render, and app surfaces inline
156
+ via `srcdoc` because the nested-iframe embed blocks `src`-URL child iframes
157
+ (`/workbench?transport=poll` and `?iframe-mode=srcdoc` force either behavior
158
+ when diagnosing). The same detection helps other buffering proxies and
159
+ nested-iframe embeds.
160
+ <!-- screenshot: docs/screenshots/amp-orb-portal.png — PMX Canvas live inside the Amp portal (to be added) -->
161
+
162
+ Any other app browser (the ChatGPT desktop app, an IDE webview, …) works the
163
+ same zero-adapter way: open the workbench URL, and add `?theme=<name|auto>` to
164
+ give that panel its own session-local default theme.
156
165
 
157
166
  The contract is host-agnostic, so a new host plugs in the same way: map its
158
167
  hooks, canvas, and session APIs onto PMX's AX primitives — no core changes.
@@ -239,6 +248,19 @@ Agents read pinned/focused context from `canvas://ax-context` /
239
248
  [`codex-app-adapter.md`](skills/pmx-canvas/references/codex-app-adapter.md) for
240
249
  the full workflow and live-test checklist.
241
250
 
251
+ ### Use inside AMPCode orbs and the Amp portal
252
+
253
+ Amp needs no adapter either. Run `pmx-canvas` as an orb service (the orb sets
254
+ `AMP_ORB` in the environment automatically) and open the workbench through the
255
+ thread's portal URL. The canvas detects the orb and the portal's nested-iframe
256
+ embed on its own: live updates arrive over the proxy-safe polling transport,
257
+ and iframe-backed nodes (HTML, graph, json-render, web artifacts) render
258
+ inline via `srcdoc` with their theme styling included. Cross-origin hosted
259
+ apps (e.g. the hosted Excalidraw MCP app) cannot be inlined and may stay
260
+ blocked by the portal embed — everything served from the canvas itself works.
261
+ Debug overrides: `/workbench?transport=poll|sse` and
262
+ `?iframe-mode=srcdoc|src`.
263
+
242
264
  ### Install the agent skill (recommended)
243
265
 
244
266
  The fastest way to get a working canvas is to install the `pmx-canvas` agent
@@ -171,6 +171,281 @@
171
171
  --c-annotation: #ffff00;
172
172
  }
173
173
 
174
+ :root[data-theme="midnight"] {
175
+ /* ── Core palette (midnight · deep indigo night) ─────────── */
176
+ --c-bg: #0A0D1C;
177
+ --c-panel: #12172E;
178
+ --c-panel-soft: #0D1224;
179
+ --c-line: #232B4A;
180
+ --c-text: #E6E9F7;
181
+ --c-text-soft: #C6CCE8;
182
+ --c-muted: #8D96BC;
183
+ --c-dim: #5B6383;
184
+ --c-accent: #8B96FF;
185
+ --c-ok: #34D399;
186
+ --c-warn: #F5C452;
187
+ --c-warn-alt: #FFB300;
188
+ --c-danger: #FF6B8A;
189
+ --c-purple: #B388FF;
190
+ --c-thinking: #B388FF;
191
+ --c-subagent: #22D3EE;
192
+ --c-input-bg: #0D1224;
193
+ --c-dot-grid: rgba(139, 150, 255, 0.08);
194
+ --c-panel-overlay: rgba(10, 13, 28, 0.6);
195
+ --c-panel-glass: rgba(18, 23, 46, 0.92);
196
+ --c-shadow: rgba(0, 0, 0, 0.3);
197
+ --c-shadow-heavy: rgba(0, 0, 0, 0.5);
198
+ --c-glow-accent: rgba(139, 150, 255, 0.15);
199
+ --c-surface-hover: rgba(255, 255, 255, 0.06);
200
+ --c-surface-subtle: rgba(255, 255, 255, 0.03);
201
+ --c-surface-th: rgba(255, 255, 255, 0.04);
202
+ --c-accent-8: rgba(139, 150, 255, 0.08);
203
+ --c-accent-10: rgba(139, 150, 255, 0.1);
204
+ --c-accent-12: rgba(139, 150, 255, 0.12);
205
+ --c-accent-15: rgba(139, 150, 255, 0.15);
206
+ --c-accent-25: rgba(139, 150, 255, 0.25);
207
+ --c-accent-30: rgba(139, 150, 255, 0.3);
208
+ --c-accent-40: rgba(139, 150, 255, 0.4);
209
+ --c-ok-10: rgba(52, 211, 153, 0.1);
210
+ --c-ok-12: rgba(52, 211, 153, 0.12);
211
+ --c-ok-20: rgba(52, 211, 153, 0.2);
212
+ --c-ok-25: rgba(52, 211, 153, 0.25);
213
+ --c-warn-10: rgba(245, 196, 82, 0.1);
214
+ --c-warn-12: rgba(245, 196, 82, 0.12);
215
+ --c-warn-15: rgba(245, 196, 82, 0.15);
216
+ --c-warn-20: rgba(245, 196, 82, 0.2);
217
+ --c-warn-22: rgba(245, 196, 82, 0.22);
218
+ --c-warn-30: rgba(245, 196, 82, 0.3);
219
+ --c-warn-60: rgba(245, 196, 82, 0.6);
220
+ --c-danger-12: rgba(255, 107, 138, 0.12);
221
+ --c-line-30: rgba(35, 43, 74, 0.3);
222
+ --c-contrast-fg: #0A0D1C;
223
+ --c-accent-hover: #A3ACFF;
224
+ --c-warn-hover: #F7D06E;
225
+ --c-canvas-wash: linear-gradient(180deg, rgba(255, 255, 255, 0.02), rgba(0, 0, 0, 0));
226
+ --c-annotation: #EDEDF7;
227
+ }
228
+
229
+ :root[data-theme="sepia"] {
230
+ /* ── Core palette (sepia · warm amber paper) ─────────────── */
231
+ --c-bg: #F2E7D5;
232
+ --c-panel: #EBDCC2;
233
+ --c-panel-soft: #E8D8BC;
234
+ --c-line: #D3C1A0;
235
+ --c-text: #2E2416;
236
+ --c-text-soft: #554833;
237
+ --c-muted: #77664C;
238
+ --c-dim: #978870;
239
+ --c-accent: #B4632C;
240
+ --c-ok: #3E7D4F;
241
+ --c-warn: #A97B1E;
242
+ --c-warn-alt: #8F6A0D;
243
+ --c-danger: #B3402E;
244
+ --c-purple: #8456B0;
245
+ --c-thinking: #8456B0;
246
+ --c-subagent: #0E7C7B;
247
+ --c-input-bg: #F7EEDD;
248
+ --c-dot-grid: rgba(46, 36, 22, 0.08);
249
+ --c-panel-overlay: rgba(242, 231, 213, 0.88);
250
+ --c-panel-glass: rgba(235, 220, 194, 0.88);
251
+ --c-shadow: rgba(46, 36, 22, 0.08);
252
+ --c-shadow-heavy: rgba(46, 36, 22, 0.16);
253
+ --c-glow-accent: rgba(180, 99, 44, 0.12);
254
+ --c-surface-hover: rgba(46, 36, 22, 0.06);
255
+ --c-surface-subtle: rgba(46, 36, 22, 0.03);
256
+ --c-surface-th: rgba(46, 36, 22, 0.04);
257
+ --c-accent-8: rgba(180, 99, 44, 0.08);
258
+ --c-accent-10: rgba(180, 99, 44, 0.1);
259
+ --c-accent-12: rgba(180, 99, 44, 0.12);
260
+ --c-accent-15: rgba(180, 99, 44, 0.15);
261
+ --c-accent-25: rgba(180, 99, 44, 0.25);
262
+ --c-accent-30: rgba(180, 99, 44, 0.3);
263
+ --c-accent-40: rgba(180, 99, 44, 0.4);
264
+ --c-ok-10: rgba(62, 125, 79, 0.1);
265
+ --c-ok-12: rgba(62, 125, 79, 0.12);
266
+ --c-ok-20: rgba(62, 125, 79, 0.2);
267
+ --c-ok-25: rgba(62, 125, 79, 0.25);
268
+ --c-warn-10: rgba(169, 123, 30, 0.1);
269
+ --c-warn-12: rgba(169, 123, 30, 0.12);
270
+ --c-warn-15: rgba(169, 123, 30, 0.15);
271
+ --c-warn-20: rgba(169, 123, 30, 0.2);
272
+ --c-warn-22: rgba(169, 123, 30, 0.22);
273
+ --c-warn-30: rgba(169, 123, 30, 0.3);
274
+ --c-warn-60: rgba(169, 123, 30, 0.6);
275
+ --c-danger-12: rgba(179, 64, 46, 0.12);
276
+ --c-line-30: rgba(211, 193, 160, 0.5);
277
+ --c-contrast-fg: #F2E7D5;
278
+ --c-accent-hover: #C4763F;
279
+ --c-warn-hover: #BD9037;
280
+ --c-canvas-wash: linear-gradient(180deg, rgba(255, 255, 255, 0.12), rgba(46, 36, 22, 0.02));
281
+ --c-annotation: #2E2416;
282
+ }
283
+
284
+ :root[data-theme="arctic"] {
285
+ /* ── Core palette (arctic · cool slate + ice) ────────────── */
286
+ --c-bg: #20242E;
287
+ --c-panel: #2A2F3C;
288
+ --c-panel-soft: #242936;
289
+ --c-line: #3A4152;
290
+ --c-text: #E9EDF4;
291
+ --c-text-soft: #CBD3E1;
292
+ --c-muted: #939DB1;
293
+ --c-dim: #667082;
294
+ --c-accent: #7FC8DE;
295
+ --c-ok: #74C596;
296
+ --c-warn: #E8C476;
297
+ --c-warn-alt: #E0A93F;
298
+ --c-danger: #D9707C;
299
+ --c-purple: #B48EAD;
300
+ --c-thinking: #B48EAD;
301
+ --c-subagent: #6FD6C4;
302
+ --c-input-bg: #242936;
303
+ --c-dot-grid: rgba(127, 200, 222, 0.08);
304
+ --c-panel-overlay: rgba(32, 36, 46, 0.6);
305
+ --c-panel-glass: rgba(42, 47, 60, 0.92);
306
+ --c-shadow: rgba(0, 0, 0, 0.3);
307
+ --c-shadow-heavy: rgba(0, 0, 0, 0.5);
308
+ --c-glow-accent: rgba(127, 200, 222, 0.15);
309
+ --c-surface-hover: rgba(255, 255, 255, 0.06);
310
+ --c-surface-subtle: rgba(255, 255, 255, 0.03);
311
+ --c-surface-th: rgba(255, 255, 255, 0.04);
312
+ --c-accent-8: rgba(127, 200, 222, 0.08);
313
+ --c-accent-10: rgba(127, 200, 222, 0.1);
314
+ --c-accent-12: rgba(127, 200, 222, 0.12);
315
+ --c-accent-15: rgba(127, 200, 222, 0.15);
316
+ --c-accent-25: rgba(127, 200, 222, 0.25);
317
+ --c-accent-30: rgba(127, 200, 222, 0.3);
318
+ --c-accent-40: rgba(127, 200, 222, 0.4);
319
+ --c-ok-10: rgba(116, 197, 150, 0.1);
320
+ --c-ok-12: rgba(116, 197, 150, 0.12);
321
+ --c-ok-20: rgba(116, 197, 150, 0.2);
322
+ --c-ok-25: rgba(116, 197, 150, 0.25);
323
+ --c-warn-10: rgba(232, 196, 118, 0.1);
324
+ --c-warn-12: rgba(232, 196, 118, 0.12);
325
+ --c-warn-15: rgba(232, 196, 118, 0.15);
326
+ --c-warn-20: rgba(232, 196, 118, 0.2);
327
+ --c-warn-22: rgba(232, 196, 118, 0.22);
328
+ --c-warn-30: rgba(232, 196, 118, 0.3);
329
+ --c-warn-60: rgba(232, 196, 118, 0.6);
330
+ --c-danger-12: rgba(217, 112, 124, 0.12);
331
+ --c-line-30: rgba(58, 65, 82, 0.3);
332
+ --c-contrast-fg: #20242E;
333
+ --c-accent-hover: #9AD5E7;
334
+ --c-warn-hover: #EDD194;
335
+ --c-canvas-wash: linear-gradient(180deg, rgba(255, 255, 255, 0.02), rgba(0, 0, 0, 0));
336
+ --c-annotation: #ECEFF4;
337
+ }
338
+
339
+ :root[data-theme="ember"] {
340
+ /* ── Core palette (ember · neutral charcoal + vivid orange-red) ─── */
341
+ --c-bg: #0B0B0C;
342
+ --c-panel: #161618;
343
+ --c-panel-soft: #101012;
344
+ --c-line: #29292C;
345
+ --c-text: #EDEBE6;
346
+ --c-text-soft: #CBC9C2;
347
+ --c-muted: #96948D;
348
+ --c-dim: #66655F;
349
+ --c-accent: #F34E1C;
350
+ --c-ok: #4CC38A;
351
+ --c-warn: #F5B944;
352
+ --c-warn-alt: #FFB300;
353
+ --c-danger: #F0566A;
354
+ --c-purple: #A78BFA;
355
+ --c-thinking: #A78BFA;
356
+ --c-subagent: #38BDF8;
357
+ --c-input-bg: #101012;
358
+ --c-dot-grid: rgba(243, 78, 28, 0.08);
359
+ --c-panel-overlay: rgba(11, 11, 12, 0.6);
360
+ --c-panel-glass: rgba(22, 22, 24, 0.92);
361
+ --c-shadow: rgba(0, 0, 0, 0.3);
362
+ --c-shadow-heavy: rgba(0, 0, 0, 0.5);
363
+ --c-glow-accent: rgba(243, 78, 28, 0.15);
364
+ --c-surface-hover: rgba(255, 255, 255, 0.06);
365
+ --c-surface-subtle: rgba(255, 255, 255, 0.03);
366
+ --c-surface-th: rgba(255, 255, 255, 0.04);
367
+ --c-accent-8: rgba(243, 78, 28, 0.08);
368
+ --c-accent-10: rgba(243, 78, 28, 0.1);
369
+ --c-accent-12: rgba(243, 78, 28, 0.12);
370
+ --c-accent-15: rgba(243, 78, 28, 0.15);
371
+ --c-accent-25: rgba(243, 78, 28, 0.25);
372
+ --c-accent-30: rgba(243, 78, 28, 0.3);
373
+ --c-accent-40: rgba(243, 78, 28, 0.4);
374
+ --c-ok-10: rgba(76, 195, 138, 0.1);
375
+ --c-ok-12: rgba(76, 195, 138, 0.12);
376
+ --c-ok-20: rgba(76, 195, 138, 0.2);
377
+ --c-ok-25: rgba(76, 195, 138, 0.25);
378
+ --c-warn-10: rgba(245, 185, 68, 0.1);
379
+ --c-warn-12: rgba(245, 185, 68, 0.12);
380
+ --c-warn-15: rgba(245, 185, 68, 0.15);
381
+ --c-warn-20: rgba(245, 185, 68, 0.2);
382
+ --c-warn-22: rgba(245, 185, 68, 0.22);
383
+ --c-warn-30: rgba(245, 185, 68, 0.3);
384
+ --c-warn-60: rgba(245, 185, 68, 0.6);
385
+ --c-danger-12: rgba(240, 86, 106, 0.12);
386
+ --c-line-30: rgba(41, 41, 44, 0.3);
387
+ --c-contrast-fg: #0B0B0C;
388
+ --c-accent-hover: #FF6B3A;
389
+ --c-warn-hover: #F7C868;
390
+ --c-canvas-wash: linear-gradient(180deg, rgba(255, 255, 255, 0.02), rgba(0, 0, 0, 0));
391
+ --c-annotation: #F2F0EB;
392
+ }
393
+
394
+ :root[data-theme="forest"] {
395
+ /* ── Core palette (forest · deep green) ──────────────────── */
396
+ --c-bg: #0C1712;
397
+ --c-panel: #14231B;
398
+ --c-panel-soft: #101D16;
399
+ --c-line: #26402F;
400
+ --c-text: #E4EFE7;
401
+ --c-text-soft: #C4D6CA;
402
+ --c-muted: #8BA695;
403
+ --c-dim: #5D7566;
404
+ --c-accent: #5CCF8F;
405
+ --c-ok: #74D99F;
406
+ --c-warn: #E0B84F;
407
+ --c-warn-alt: #D9A421;
408
+ --c-danger: #E57373;
409
+ --c-purple: #A88FE0;
410
+ --c-thinking: #A88FE0;
411
+ --c-subagent: #4FD8CE;
412
+ --c-input-bg: #101D16;
413
+ --c-dot-grid: rgba(92, 207, 143, 0.08);
414
+ --c-panel-overlay: rgba(12, 23, 18, 0.6);
415
+ --c-panel-glass: rgba(20, 35, 27, 0.92);
416
+ --c-shadow: rgba(0, 0, 0, 0.3);
417
+ --c-shadow-heavy: rgba(0, 0, 0, 0.5);
418
+ --c-glow-accent: rgba(92, 207, 143, 0.15);
419
+ --c-surface-hover: rgba(255, 255, 255, 0.06);
420
+ --c-surface-subtle: rgba(255, 255, 255, 0.03);
421
+ --c-surface-th: rgba(255, 255, 255, 0.04);
422
+ --c-accent-8: rgba(92, 207, 143, 0.08);
423
+ --c-accent-10: rgba(92, 207, 143, 0.1);
424
+ --c-accent-12: rgba(92, 207, 143, 0.12);
425
+ --c-accent-15: rgba(92, 207, 143, 0.15);
426
+ --c-accent-25: rgba(92, 207, 143, 0.25);
427
+ --c-accent-30: rgba(92, 207, 143, 0.3);
428
+ --c-accent-40: rgba(92, 207, 143, 0.4);
429
+ --c-ok-10: rgba(116, 217, 159, 0.1);
430
+ --c-ok-12: rgba(116, 217, 159, 0.12);
431
+ --c-ok-20: rgba(116, 217, 159, 0.2);
432
+ --c-ok-25: rgba(116, 217, 159, 0.25);
433
+ --c-warn-10: rgba(224, 184, 79, 0.1);
434
+ --c-warn-12: rgba(224, 184, 79, 0.12);
435
+ --c-warn-15: rgba(224, 184, 79, 0.15);
436
+ --c-warn-20: rgba(224, 184, 79, 0.2);
437
+ --c-warn-22: rgba(224, 184, 79, 0.22);
438
+ --c-warn-30: rgba(224, 184, 79, 0.3);
439
+ --c-warn-60: rgba(224, 184, 79, 0.6);
440
+ --c-danger-12: rgba(229, 115, 115, 0.12);
441
+ --c-line-30: rgba(38, 64, 47, 0.3);
442
+ --c-contrast-fg: #0C1712;
443
+ --c-accent-hover: #7BDAA4;
444
+ --c-warn-hover: #E8C871;
445
+ --c-canvas-wash: linear-gradient(180deg, rgba(255, 255, 255, 0.02), rgba(0, 0, 0, 0));
446
+ --c-annotation: #EDF5EF;
447
+ }
448
+
174
449
  * {
175
450
  box-sizing: border-box;
176
451
  margin: 0;
@@ -677,10 +952,141 @@ body,
677
952
  max-width: 100%;
678
953
  }
679
954
 
955
+ /* Positioning parent for a toolbar popover menu. Every menu MUST live inside
956
+ one of these wrapped around its trigger — anchoring to a wider ancestor
957
+ opens the menu far from the button (0.4.2 report Finding T). */
958
+ .toolbar-menu-anchor {
959
+ position: relative;
960
+ display: inline-flex;
961
+ }
962
+
680
963
  .canvas-toolbar button svg {
681
964
  display: block;
682
965
  }
683
966
 
967
+ /* ── Toolbar popover menus (theme picker + narrow-screen More menu) ── */
968
+ .toolbar-menu {
969
+ position: absolute;
970
+ top: calc(100% + 8px);
971
+ right: 0;
972
+ z-index: 1200;
973
+ display: flex;
974
+ flex-direction: column;
975
+ gap: 2px;
976
+ min-width: 224px;
977
+ max-height: min(70vh, 560px);
978
+ overflow-y: auto;
979
+ padding: 6px;
980
+ background: var(--c-panel-glass);
981
+ backdrop-filter: blur(12px);
982
+ border: 1px solid var(--c-line);
983
+ border-radius: var(--radius);
984
+ box-shadow: 0 18px 48px var(--c-shadow-heavy);
985
+ }
986
+
987
+ .toolbar-menu-item {
988
+ display: flex;
989
+ align-items: center;
990
+ gap: 10px;
991
+ width: 100%;
992
+ min-height: 40px;
993
+ padding: 8px 12px;
994
+ background: none;
995
+ border: 1px solid transparent;
996
+ border-radius: var(--radius-sm);
997
+ color: var(--c-text-soft);
998
+ font: inherit;
999
+ font-size: 13px;
1000
+ text-align: left;
1001
+ cursor: pointer;
1002
+ }
1003
+
1004
+ .toolbar-menu-item:hover {
1005
+ background: var(--c-surface-hover);
1006
+ color: var(--c-text);
1007
+ border-color: var(--c-line);
1008
+ }
1009
+
1010
+ .toolbar-menu-item.active {
1011
+ color: var(--c-accent);
1012
+ }
1013
+
1014
+ .toolbar-menu-item svg {
1015
+ flex-shrink: 0;
1016
+ }
1017
+
1018
+ .toolbar-menu-check {
1019
+ margin-left: auto;
1020
+ color: var(--c-accent);
1021
+ }
1022
+
1023
+ .toolbar-menu-separator {
1024
+ height: 1px;
1025
+ margin: 4px 8px;
1026
+ background: var(--c-line);
1027
+ flex-shrink: 0;
1028
+ }
1029
+
1030
+ .toolbar-menu-heading {
1031
+ padding: 6px 12px 2px;
1032
+ font-size: 10px;
1033
+ letter-spacing: 0.08em;
1034
+ text-transform: uppercase;
1035
+ color: var(--c-dim);
1036
+ }
1037
+
1038
+ .theme-swatch {
1039
+ display: inline-flex;
1040
+ align-items: center;
1041
+ justify-content: center;
1042
+ width: 18px;
1043
+ height: 18px;
1044
+ border: 1px solid var(--c-line);
1045
+ border-radius: 50%;
1046
+ flex-shrink: 0;
1047
+ }
1048
+
1049
+ .theme-swatch-dot {
1050
+ width: 7px;
1051
+ height: 7px;
1052
+ border-radius: 50%;
1053
+ }
1054
+
1055
+ .toolbar-more-glyph {
1056
+ display: block;
1057
+ font-size: 16px;
1058
+ font-weight: 700;
1059
+ line-height: 1;
1060
+ }
1061
+
1062
+ /* Wide screens: the full two-bar toolbar; the More trigger stays hidden. */
1063
+ .toolbar-collapsible {
1064
+ display: contents;
1065
+ }
1066
+
1067
+ .toolbar-more {
1068
+ display: none;
1069
+ }
1070
+
1071
+ /* Narrow screens / embedded panels: ONE compact bar (brand, status, view
1072
+ controls) — every secondary control folds into the More menu instead of
1073
+ wrapping into a second toolbar row. */
1074
+ @media (max-width: 900px) {
1075
+ .toolbar-collapsible {
1076
+ display: none;
1077
+ }
1078
+
1079
+ .toolbar-actions-bar {
1080
+ display: none;
1081
+ }
1082
+
1083
+ .toolbar-more {
1084
+ /* inline-flex (NOT contents): this span is also the positioning anchor
1085
+ for the More menu — display:contents cannot anchor absolute children. */
1086
+ display: inline-flex;
1087
+ }
1088
+ }
1089
+
684
1090
  @media (max-width: 900px) {
685
1091
  .toolbar-tooltip {
686
1092
  min-width: 150px;