@1agh/maude 0.23.0 → 0.25.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/README.md +34 -1
- package/cli/bin/maude.mjs +3 -0
- package/cli/commands/cache.mjs +181 -0
- package/cli/commands/cache.test.mjs +166 -0
- package/cli/commands/design-link.test.mjs +32 -2
- package/cli/commands/design.mjs +95 -4
- package/cli/commands/design.test.mjs +56 -0
- package/cli/commands/help.mjs +34 -0
- package/cli/commands/hub.mjs +255 -30
- package/cli/commands/hub.test.mjs +126 -2
- package/cli/commands/init.mjs +3 -0
- package/cli/commands/preflight.mjs +11 -0
- package/cli/commands/preflight.test.mjs +46 -0
- package/cli/commands/scenario-report.mjs +45 -0
- package/cli/lib/cache.mjs +407 -0
- package/cli/lib/cache.test.mjs +303 -0
- package/cli/lib/design-link.mjs +74 -10
- package/cli/lib/flow-design-integration.test.mjs +165 -0
- package/cli/lib/gitignore-block.mjs +90 -0
- package/cli/lib/gitignore-block.test.mjs +123 -0
- package/cli/lib/plugin-cli-reachability.test.mjs +56 -0
- package/cli/lib/preflight.mjs +41 -10
- package/package.json +8 -8
- package/plugins/design/dependencies.json +30 -2
- package/plugins/design/dev-server/ai-banner.tsx +2 -2
- package/plugins/design/dev-server/annotations-context-toolbar.tsx +763 -123
- package/plugins/design/dev-server/annotations-layer.tsx +1624 -208
- package/plugins/design/dev-server/api.ts +123 -4
- package/plugins/design/dev-server/bin/_enumerate-artboards-playwright.mjs +2 -2
- package/plugins/design/dev-server/bin/_html-playwright.mjs +2 -2
- package/plugins/design/dev-server/bin/_pdf-playwright.mjs +25 -8
- package/plugins/design/dev-server/bin/_png-playwright.mjs +20 -20
- package/plugins/design/dev-server/bin/_pptx-playwright.mjs +2 -2
- package/plugins/design/dev-server/bin/_pw-launch.mjs +61 -0
- package/plugins/design/dev-server/bin/_screenshot-playwright.mjs +2 -2
- package/plugins/design/dev-server/bin/_svg-playwright.mjs +125 -19
- package/plugins/design/dev-server/bin/preflight.sh +21 -10
- package/plugins/design/dev-server/bin/prep.sh +211 -0
- package/plugins/design/dev-server/bin/scenario-report.mjs +209 -0
- package/plugins/design/dev-server/bin/screenshot.sh +18 -1
- package/plugins/design/dev-server/bin/smoke.sh +208 -23
- package/plugins/design/dev-server/canvas-arrowheads.ts +220 -0
- package/plugins/design/dev-server/canvas-comment-mount.tsx +8 -24
- package/plugins/design/dev-server/canvas-cursors.ts +173 -0
- package/plugins/design/dev-server/canvas-icons.tsx +299 -0
- package/plugins/design/dev-server/canvas-lib.tsx +25 -21
- package/plugins/design/dev-server/canvas-meta.schema.json +20 -0
- package/plugins/design/dev-server/canvas-shell.tsx +404 -66
- package/plugins/design/dev-server/client/app.jsx +1231 -416
- package/plugins/design/dev-server/client/styles/3-shell.css +10 -0
- package/plugins/design/dev-server/collab/index.ts +87 -9
- package/plugins/design/dev-server/collab/persistence.ts +34 -3
- package/plugins/design/dev-server/collab/registry.ts +80 -2
- package/plugins/design/dev-server/collab/room.ts +21 -8
- package/plugins/design/dev-server/context-menu.tsx +167 -23
- package/plugins/design/dev-server/context.ts +24 -0
- package/plugins/design/dev-server/contextual-toolbar.tsx +7 -7
- package/plugins/design/dev-server/dist/client.bundle.js +364 -29
- package/plugins/design/dev-server/dist/comment-mount.js +78 -33
- package/plugins/design/dev-server/dist/styles.css +16 -0
- package/plugins/design/dev-server/equal-spacing-handles.tsx +1 -1
- package/plugins/design/dev-server/export-dialog.tsx +208 -18
- package/plugins/design/dev-server/exporters/html.ts +4 -1
- package/plugins/design/dev-server/exporters/index.ts +4 -1
- package/plugins/design/dev-server/exporters/pdf.ts +7 -1
- package/plugins/design/dev-server/exporters/png.ts +21 -2
- package/plugins/design/dev-server/exporters/pptx.ts +330 -201
- package/plugins/design/dev-server/exporters/scope.ts +107 -11
- package/plugins/design/dev-server/exporters/svg.ts +4 -1
- package/plugins/design/dev-server/fs-watch.ts +1 -0
- package/plugins/design/dev-server/http.ts +299 -28
- package/plugins/design/dev-server/input-router.tsx +35 -5
- package/plugins/design/dev-server/participants-chrome.tsx +10 -10
- package/plugins/design/dev-server/server.ts +123 -1
- package/plugins/design/dev-server/sync/agent.ts +95 -0
- package/plugins/design/dev-server/sync/codec.ts +155 -0
- package/plugins/design/dev-server/sync/connection-state.ts +203 -0
- package/plugins/design/dev-server/sync/index.ts +479 -35
- package/plugins/design/dev-server/sync/materialize.ts +62 -0
- package/plugins/design/dev-server/sync/migrate-seed.ts +163 -0
- package/plugins/design/dev-server/sync/origins.ts +57 -0
- package/plugins/design/dev-server/sync/projection.ts +368 -0
- package/plugins/design/dev-server/sync/status.ts +115 -0
- package/plugins/design/dev-server/sync/untrusted.ts +153 -0
- package/plugins/design/dev-server/test/_helpers.ts +6 -2
- package/plugins/design/dev-server/test/annotations-draw-modifiers.test.ts +206 -0
- package/plugins/design/dev-server/test/annotations-layer.test.ts +311 -0
- package/plugins/design/dev-server/test/annotations-roundtrip.test.ts +519 -0
- package/plugins/design/dev-server/test/canvas-cursors.test.ts +162 -0
- package/plugins/design/dev-server/test/canvas-origin-gate.test.ts +76 -0
- package/plugins/design/dev-server/test/canvas-route.test.ts +4 -1
- package/plugins/design/dev-server/test/collab-reseed-guard.test.ts +78 -0
- package/plugins/design/dev-server/test/collab-room.test.ts +21 -10
- package/plugins/design/dev-server/test/csp-canvas-shell.test.ts +46 -0
- package/plugins/design/dev-server/test/exporters/png.test.ts +24 -1
- package/plugins/design/dev-server/test/exporters/pptx-deck.test.ts +112 -0
- package/plugins/design/dev-server/test/exporters/pw-launch.test.ts +49 -0
- package/plugins/design/dev-server/test/exporters/scope.test.ts +0 -0
- package/plugins/design/dev-server/test/fixtures/phase-20-annotations.svg +1 -0
- package/plugins/design/dev-server/test/fixtures/phase-21-annotations.svg +1 -0
- package/plugins/design/dev-server/test/input-router.test.ts +32 -4
- package/plugins/design/dev-server/test/sanitize-annotation-svg.test.ts +132 -0
- package/plugins/design/dev-server/test/shared-doc-convergence.test.ts +265 -0
- package/plugins/design/dev-server/test/shared-doc-foundation.test.ts +63 -0
- package/plugins/design/dev-server/test/shared-doc-migrate.test.ts +160 -0
- package/plugins/design/dev-server/test/shared-doc-projection.test.ts +238 -0
- package/plugins/design/dev-server/test/sync-connection-state.test.ts +146 -0
- package/plugins/design/dev-server/test/sync-meta-codec.test.ts +123 -0
- package/plugins/design/dev-server/test/sync-runtime.test.ts +531 -4
- package/plugins/design/dev-server/test/sync-status.test.ts +80 -0
- package/plugins/design/dev-server/test/sync-untrusted.test.ts +104 -0
- package/plugins/design/dev-server/test/use-annotation-resize.test.ts +200 -0
- package/plugins/design/dev-server/test/use-tool-mode.test.tsx +39 -13
- package/plugins/design/dev-server/tool-palette.tsx +155 -24
- package/plugins/design/dev-server/undo-hud.tsx +4 -4
- package/plugins/design/dev-server/undo-stack.ts +20 -4
- package/plugins/design/dev-server/use-annotation-resize.tsx +219 -61
- package/plugins/design/dev-server/use-tool-mode.tsx +68 -19
- package/plugins/design/dev-server/ws.ts +40 -1
- package/plugins/design/templates/_shell.html +85 -10
- package/plugins/design/templates/canvas.tsx.template +13 -0
- package/plugins/design/templates/design-system-inspiration/SUB-AGENT-PROMPTS.md +14 -7
- package/plugins/flow/.claude-plugin/config.schema.json +5 -0
- package/plugins/flow/templates/ai-skeleton/README.md +1 -1
- package/plugins/flow/templates/ai-skeleton/gitignore +10 -0
- package/plugins/flow/templates/ai-skeleton/scenarios/README.md +3 -1
- package/plugins/flow/templates/ai-skeleton/workflows.config.json +2 -1
|
@@ -98,25 +98,63 @@ import { useUndoStack } from './use-undo-stack.tsx';
|
|
|
98
98
|
// plane would otherwise scale a 2 px outline to 0.84 px at 42 % zoom (subpixel
|
|
99
99
|
// = invisible). No per-element class stamping is used.
|
|
100
100
|
|
|
101
|
-
// HUD
|
|
101
|
+
// HUD / chrome token block. System-review 2026-05-27 (D-4) flagged that the
|
|
102
102
|
// dev-server chrome (toolbar + minimap + halos + marquee + AI banner) used
|
|
103
103
|
// `var(--accent, …)` which inherited the canvas DS palette — a violet StudyFi
|
|
104
104
|
// canvas turned the floating cursor toolbar violet. The HUD owns its own
|
|
105
105
|
// `--maude-hud-*` token family, set on `:root` of the canvas iframe document
|
|
106
|
-
// here. Canvas DSs do NOT define
|
|
107
|
-
// this block regardless of what the imported `:root { --accent: … }`
|
|
106
|
+
// here. Canvas DSs do NOT define these names, so HUD/chrome CSS resolves
|
|
107
|
+
// against this block regardless of what the imported `:root { --accent: … }`
|
|
108
|
+
// looks like.
|
|
108
109
|
//
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
110
|
+
// Two sub-families:
|
|
111
|
+
// • `--maude-hud-accent*` — theme-agnostic brand orange-rust. Defaults match
|
|
112
|
+
// the existing inline fallback (`#d63b1f`) so the accent never changes with
|
|
113
|
+
// the theme. Users who want to re-theme the HUD can set these via a
|
|
114
|
+
// `<style>` block AFTER this one (CSS cascade — later wins).
|
|
115
|
+
// • `--maude-chrome-*` — NEUTRAL surface/text/border family that DOES follow
|
|
116
|
+
// the Maude chrome theme (system-review 2026-05-28 D9). The whole canvas-
|
|
117
|
+
// shell chrome (workspace plane, dotted grid, floating toolbar, minimap,
|
|
118
|
+
// zoom HUD, popovers, halos, context menu, undo HUD, presence chrome) reads
|
|
119
|
+
// these instead of the DS `--bg-*`/`--fg-*` palette, so the chrome flips
|
|
120
|
+
// dark↔light with the rest of the dev-server while ARTBOARDS keep the theme
|
|
121
|
+
// their design system defines. The set is selected by a `data-maude-theme`
|
|
122
|
+
// attribute on the iframe `documentElement`, propagated over the existing
|
|
123
|
+
// `dgn:*` postMessage bridge (see CanvasRouter onMessage + app.jsx).
|
|
124
|
+
//
|
|
125
|
+
// Values mirror the Maude app-chrome neutrals (client/styles/1-tokens.css) so
|
|
126
|
+
// the in-iframe chrome and the outer shell read as one product. The DARK set is
|
|
127
|
+
// also the default (attribute absent / "dark") so a canvas that never receives
|
|
128
|
+
// a theme message renders coherent-dark — matching the dev-server's own default
|
|
129
|
+
// theme (readInitialTheme() → 'dark'). DDR — mirrors the `--maude-hud-*`
|
|
130
|
+
// precedent; the `data-maude-theme` attribute is deliberately SEPARATE from the
|
|
131
|
+
// DS `data-theme` so chrome theming never touches artboard palettes.
|
|
113
132
|
const HUD_TOKENS_CSS = `
|
|
114
|
-
:root
|
|
133
|
+
:root,
|
|
134
|
+
:root[data-maude-theme="dark"] {
|
|
115
135
|
--maude-hud-accent: #d63b1f;
|
|
116
136
|
--maude-hud-accent-hover: #b8331b;
|
|
117
137
|
--maude-hud-accent-active: #962a16;
|
|
118
138
|
--maude-hud-accent-fg: #ffffff;
|
|
119
139
|
--maude-hud-accent-tint: color-mix(in oklab, #d63b1f 14%, transparent);
|
|
140
|
+
|
|
141
|
+
--maude-chrome-bg-0: oklch(13% 0.012 60);
|
|
142
|
+
--maude-chrome-bg-1: oklch(17% 0.014 60);
|
|
143
|
+
--maude-chrome-bg-2: oklch(20% 0.016 60);
|
|
144
|
+
--maude-chrome-fg-0: oklch(94% 0.014 80);
|
|
145
|
+
--maude-chrome-fg-1: oklch(78% 0.014 80);
|
|
146
|
+
--maude-chrome-border: oklch(28% 0.018 60);
|
|
147
|
+
--maude-chrome-shadow: rgba(0, 0, 0, 0.45);
|
|
148
|
+
--maude-chrome-font-mono: 'Berkeley Mono', 'TX-02', 'JetBrains Mono', 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
149
|
+
}
|
|
150
|
+
:root[data-maude-theme="light"] {
|
|
151
|
+
--maude-chrome-bg-0: oklch(97.5% 0.008 78);
|
|
152
|
+
--maude-chrome-bg-1: oklch(95.5% 0.010 78);
|
|
153
|
+
--maude-chrome-bg-2: oklch(93.0% 0.012 78);
|
|
154
|
+
--maude-chrome-fg-0: oklch(20% 0.020 50);
|
|
155
|
+
--maude-chrome-fg-1: oklch(38% 0.018 50);
|
|
156
|
+
--maude-chrome-border: oklch(86% 0.014 70);
|
|
157
|
+
--maude-chrome-shadow: color-mix(in oklab, oklch(20% 0.020 50) 14%, transparent);
|
|
120
158
|
}
|
|
121
159
|
`;
|
|
122
160
|
|
|
@@ -138,7 +176,7 @@ const HALO_CSS = `
|
|
|
138
176
|
elements. NO ring, NO ticks. Synchronous paint (no debounce). */
|
|
139
177
|
.dc-cv-halo--hover {
|
|
140
178
|
border: 1.5px solid color-mix(in oklab, var(--maude-hud-accent, #0d99ff) 60%, transparent);
|
|
141
|
-
box-shadow: inset 0 0 0 1px var(--bg-0, #ffffff);
|
|
179
|
+
box-shadow: inset 0 0 0 1px var(--maude-chrome-bg-0, #ffffff);
|
|
142
180
|
}
|
|
143
181
|
/* Selected (single) — 2px solid + 18% ring halo + 4 filled corner ticks.
|
|
144
182
|
Ticks are <i class="tick tick-*"> children at inset:-3px, 8x8, accent fill. */
|
|
@@ -152,7 +190,7 @@ const HALO_CSS = `
|
|
|
152
190
|
height: 8px;
|
|
153
191
|
background: var(--maude-hud-accent, #0d99ff);
|
|
154
192
|
border-radius: 1px;
|
|
155
|
-
box-shadow: 0 0 0 1px var(--bg-0, #ffffff);
|
|
193
|
+
box-shadow: 0 0 0 1px var(--maude-chrome-bg-0, #ffffff);
|
|
156
194
|
}
|
|
157
195
|
.dc-cv-halo--selected .tick-tl { top: -3px; left: -3px; }
|
|
158
196
|
.dc-cv-halo--selected .tick-tr { top: -3px; right: -3px; }
|
|
@@ -186,7 +224,7 @@ const HALO_CSS = `
|
|
|
186
224
|
height: 6px;
|
|
187
225
|
background: var(--maude-hud-accent, #0d99ff);
|
|
188
226
|
border-radius: 1px;
|
|
189
|
-
box-shadow: 0 0 0 1px var(--bg-0, #ffffff);
|
|
227
|
+
box-shadow: 0 0 0 1px var(--maude-chrome-bg-0, #ffffff);
|
|
190
228
|
}
|
|
191
229
|
.dc-cv-group-bbox .tick-tl { top: -3px; left: -3px; }
|
|
192
230
|
.dc-cv-group-bbox .tick-tr { top: -3px; right: -3px; }
|
|
@@ -215,53 +253,19 @@ const HALO_CSS = `
|
|
|
215
253
|
transition: none !important;
|
|
216
254
|
}
|
|
217
255
|
}
|
|
218
|
-
/*
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
*
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
--cursor-rect: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><line x1='0.5' y1='8' x2='15.5' y2='8' stroke='%23111' stroke-width='1'/><line x1='8' y1='0.5' x2='8' y2='15.5' stroke='%23111' stroke-width='1'/><rect x='10.5' y='10.5' width='4' height='3' fill='white' stroke='%23111' stroke-width='1'/></svg>") 8 8;
|
|
232
|
-
--cursor-ellipse: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><line x1='0.5' y1='8' x2='15.5' y2='8' stroke='%23111' stroke-width='1'/><line x1='8' y1='0.5' x2='8' y2='15.5' stroke='%23111' stroke-width='1'/><ellipse cx='12.5' cy='12' rx='2.5' ry='1.5' fill='white' stroke='%23111' stroke-width='1'/></svg>") 8 8;
|
|
233
|
-
--cursor-arrow: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><line x1='0.5' y1='8' x2='15.5' y2='8' stroke='%23111' stroke-width='1'/><line x1='8' y1='0.5' x2='8' y2='15.5' stroke='%23111' stroke-width='1'/><path d='M10 13 L15 13 M12.5 11 L15 13 L12.5 15' fill='none' stroke='%23111' stroke-width='1' stroke-linecap='round' stroke-linejoin='round'/></svg>") 8 8;
|
|
234
|
-
--cursor-eraser: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><rect x='3' y='9' width='8' height='5' rx='1' fill='%23ffb3da' stroke='%23111' stroke-width='1' transform='rotate(-20 7 11.5)'/><rect x='3' y='9' width='8' height='2' fill='%23d63b6e' transform='rotate(-20 7 10)' opacity='0.6'/></svg>") 8 14;
|
|
235
|
-
--cursor-comment: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><path d='M3 3 L14 3 L14 11 L8 11 L4 14 L4 11 L3 11 Z' fill='white' stroke='%23111' stroke-width='1' stroke-linejoin='round'/></svg>") 4 4;
|
|
236
|
-
}
|
|
237
|
-
.dc-canvas[data-active-tool="comment"],
|
|
238
|
-
.dc-canvas[data-active-tool="comment"] * {
|
|
239
|
-
cursor: var(--cursor-comment), crosshair !important;
|
|
240
|
-
}
|
|
241
|
-
.dc-canvas[data-active-tool="pen"],
|
|
242
|
-
.dc-canvas[data-active-tool="pen"] * {
|
|
243
|
-
cursor: var(--cursor-pen), crosshair !important;
|
|
244
|
-
}
|
|
245
|
-
.dc-canvas[data-active-tool="rect"],
|
|
246
|
-
.dc-canvas[data-active-tool="rect"] * {
|
|
247
|
-
cursor: var(--cursor-rect), crosshair !important;
|
|
248
|
-
}
|
|
249
|
-
.dc-canvas[data-active-tool="ellipse"],
|
|
250
|
-
.dc-canvas[data-active-tool="ellipse"] * {
|
|
251
|
-
cursor: var(--cursor-ellipse), crosshair !important;
|
|
252
|
-
}
|
|
253
|
-
.dc-canvas[data-active-tool="arrow"],
|
|
254
|
-
.dc-canvas[data-active-tool="arrow"] * {
|
|
255
|
-
cursor: var(--cursor-arrow), crosshair !important;
|
|
256
|
-
}
|
|
257
|
-
.dc-canvas[data-active-tool="hand"],
|
|
258
|
-
.dc-canvas[data-active-tool="hand"] * {
|
|
259
|
-
cursor: grab !important;
|
|
260
|
-
}
|
|
261
|
-
.dc-canvas[data-active-tool="eraser"],
|
|
262
|
-
.dc-canvas[data-active-tool="eraser"] * {
|
|
263
|
-
cursor: var(--cursor-eraser), cell !important;
|
|
264
|
-
}
|
|
256
|
+
/* Phase 24 — per-tool cursors are owned SOLELY by use-tool-mode.tsx, which
|
|
257
|
+
injects ONE unified Kenney-glyph rule ('* { cursor: <tool> !important }')
|
|
258
|
+
into the same document. The old T22 per-tool '.dc-canvas[data-active-tool=…]'
|
|
259
|
+
rules used to live here, but their higher specificity ('.class[attr] *'
|
|
260
|
+
beats '*') silently SHADOWED the new cursors for comment/pen/arrow/hand/
|
|
261
|
+
eraser — they kept showing the stale 16px glyphs (or their native fallback)
|
|
262
|
+
while move/shape/sticky/text (which had no shadowing rule) correctly showed
|
|
263
|
+
the Kenney set. Removing them lets the single source of truth win for every
|
|
264
|
+
tool. 'data-active-tool' is still set on '.dc-canvas' (see CanvasCore) and
|
|
265
|
+
keyed by canvas-lib's move-mode label cursor + use-cursor-modifiers — those
|
|
266
|
+
stay. See DDR-067 / Phase 24. NOTE: keep this comment backtick-free — it
|
|
267
|
+
lives inside the HALO_CSS template literal and a stray backtick closes it
|
|
268
|
+
(bun parse fail, DDR-067 §6). */
|
|
265
269
|
|
|
266
270
|
/* T31 — Level of detail. Below 0.35 zoom we hide pre-attentive chrome that
|
|
267
271
|
becomes visual noise (corner ticks, distance pills, active-artboard ring,
|
|
@@ -313,6 +317,39 @@ export function CanvasShell({
|
|
|
313
317
|
children: ReactNode;
|
|
314
318
|
}) {
|
|
315
319
|
ensureHaloStyles();
|
|
320
|
+
|
|
321
|
+
// theme-default-follow — every un-pinned artboard tracks the chrome theme.
|
|
322
|
+
// When it flips (the theme postMessage handler in CanvasRouter updates
|
|
323
|
+
// `data-maude-theme` on <html>), re-theme them. A single observer per canvas
|
|
324
|
+
// (cleaned up on unmount) — no per-artboard listeners to leak. The rule is a
|
|
325
|
+
// CSS block keyed by data-dc-screen (see rebuildArtboardThemeStyle), so it
|
|
326
|
+
// survives React re-renders without flicker.
|
|
327
|
+
useEffect(() => {
|
|
328
|
+
if (typeof MutationObserver === 'undefined' || typeof document === 'undefined') return;
|
|
329
|
+
const obs = new MutationObserver(() => applyArtboardFollowers());
|
|
330
|
+
obs.observe(document.documentElement, {
|
|
331
|
+
attributes: true,
|
|
332
|
+
attributeFilter: ['data-maude-theme'],
|
|
333
|
+
});
|
|
334
|
+
// Initial follow — theme every un-pinned artboard once the DS stylesheet has
|
|
335
|
+
// parsed. The observer only fires on a `data-maude-theme` CHANGE, which may
|
|
336
|
+
// not happen on first paint (the chrome theme can already equal the canvas
|
|
337
|
+
// default), so first paint needs its own trigger. `window 'load'` guarantees
|
|
338
|
+
// every <link>/<style> sheet is parsed → collectThemeDeclarations sees the
|
|
339
|
+
// DS tokens. Runs immediately if the document is already complete.
|
|
340
|
+
let onLoad: (() => void) | null = null;
|
|
341
|
+
if (document.readyState === 'complete') {
|
|
342
|
+
applyArtboardFollowers();
|
|
343
|
+
} else {
|
|
344
|
+
onLoad = () => applyArtboardFollowers();
|
|
345
|
+
window.addEventListener('load', onLoad, { once: true });
|
|
346
|
+
}
|
|
347
|
+
return () => {
|
|
348
|
+
obs.disconnect();
|
|
349
|
+
if (onLoad) window.removeEventListener('load', onLoad);
|
|
350
|
+
};
|
|
351
|
+
}, []);
|
|
352
|
+
|
|
316
353
|
// ToolProvider is mounted by DesignCanvas one level up (so the viewport
|
|
317
354
|
// controller's `isPanDragActive` predicate can read the live tool state).
|
|
318
355
|
// SelectionSetProvider is mounted via MaybeSelectionSetProvider — the shell-
|
|
@@ -659,6 +696,235 @@ function CanvasCore({
|
|
|
659
696
|
);
|
|
660
697
|
}
|
|
661
698
|
|
|
699
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
700
|
+
// Artboard theme (D9 + theme-default-follow, 2026-05-31). The status-bar theme
|
|
701
|
+
// toggle (vpravo dole) is the SINGLE SOURCE OF TRUTH: the canvas-shell chrome
|
|
702
|
+
// AND every artboard follow it by default via `data-maude-theme`. The right-
|
|
703
|
+
// click `Theme ▸ DS default / Light / Dark / Follow chrome` submenu PINS one
|
|
704
|
+
// artboard to override that default — re-themed by stamping its content wrapper
|
|
705
|
+
// with the DS's theme-wrapper convention. (Before this date the default was the
|
|
706
|
+
// inverse — artboards kept their own DS theme and following was opt-in — which
|
|
707
|
+
// produced the recurring "canvas dark / chrome light" mismatch.)
|
|
708
|
+
//
|
|
709
|
+
// DS theme-wrapper conventions vary (`.mdcc[data-theme]`, `.app[data-theme]`,
|
|
710
|
+
// bare `[data-theme]`, …) and there's no reliable config flag, so we DETECT it
|
|
711
|
+
// with a hidden computed-style probe: stamp a throwaway nested <div> with each
|
|
712
|
+
// candidate `<class>[data-theme=light|dark]` and keep the first whose resolved
|
|
713
|
+
// `--bg-0` differs between light and dark. Because the probe tests a NON-root
|
|
714
|
+
// element, "supported" is exactly "stamping one artboard will work" — a DS that
|
|
715
|
+
// only themes `:root[data-theme]` correctly reports unsupported (you genuinely
|
|
716
|
+
// can't theme a single artboard there).
|
|
717
|
+
|
|
718
|
+
interface DsThemeSupport {
|
|
719
|
+
supported: boolean;
|
|
720
|
+
/** The class the DS scopes its theme blocks to (`''` = bare `[data-theme]`). */
|
|
721
|
+
wrapperClass: string;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
let _dsThemeSupport: DsThemeSupport | null = null;
|
|
725
|
+
|
|
726
|
+
function detectDsThemeSupport(): DsThemeSupport {
|
|
727
|
+
if (_dsThemeSupport) return _dsThemeSupport;
|
|
728
|
+
const fallback: DsThemeSupport = { supported: false, wrapperClass: '' };
|
|
729
|
+
if (typeof document === 'undefined' || !document.body) return fallback;
|
|
730
|
+
try {
|
|
731
|
+
const host = document.createElement('div');
|
|
732
|
+
host.setAttribute('aria-hidden', 'true');
|
|
733
|
+
host.style.cssText =
|
|
734
|
+
'position:absolute;left:-9999px;top:0;width:0;height:0;overflow:hidden;pointer-events:none;';
|
|
735
|
+
document.body.appendChild(host);
|
|
736
|
+
const read = (cls: string, theme: string): string => {
|
|
737
|
+
const el = document.createElement('div');
|
|
738
|
+
if (cls) el.className = cls;
|
|
739
|
+
el.setAttribute('data-theme', theme);
|
|
740
|
+
host.appendChild(el);
|
|
741
|
+
const v = getComputedStyle(el).getPropertyValue('--bg-0').trim();
|
|
742
|
+
host.removeChild(el);
|
|
743
|
+
return v;
|
|
744
|
+
};
|
|
745
|
+
// Bare attribute first (cleanest), then the common class conventions.
|
|
746
|
+
let found = fallback;
|
|
747
|
+
for (const cls of ['', 'mdcc', 'app']) {
|
|
748
|
+
const light = read(cls, 'light');
|
|
749
|
+
const dark = read(cls, 'dark');
|
|
750
|
+
if (light && dark && light !== dark) {
|
|
751
|
+
found = { supported: true, wrapperClass: cls };
|
|
752
|
+
break;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
document.body.removeChild(host);
|
|
756
|
+
// Cache only a POSITIVE detection. With follow-chrome now the artboard
|
|
757
|
+
// default, this can run on first paint — before the DS stylesheet has
|
|
758
|
+
// parsed — where the probe reads empty tokens and wrongly reports
|
|
759
|
+
// unsupported. Caching that would permanently disable theming; instead
|
|
760
|
+
// re-probe until support is confirmed (or the DS genuinely has one theme).
|
|
761
|
+
if (found.supported) _dsThemeSupport = found;
|
|
762
|
+
return found;
|
|
763
|
+
} catch {
|
|
764
|
+
return fallback;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// Why not just stamp `data-theme` on the `.dc-artboard` element? Two reasons,
|
|
769
|
+
// both found the hard way: (1) React OWNS the artboard <article> + the canvas
|
|
770
|
+
// content's `rootClass` wrapper — it reconciles their className/attrs back to
|
|
771
|
+
// the JSX values on every re-render, wiping any imperative mutation; and (2)
|
|
772
|
+
// the canvas content carries its OWN `rootClass[data-theme]` wrapper (the DS
|
|
773
|
+
// default), which re-establishes the default tokens BELOW the artboard, so
|
|
774
|
+
// setting `data-theme` on the outer article never reaches the content. The
|
|
775
|
+
// robust mechanism is a single injected <style> keyed by the STABLE
|
|
776
|
+
// `data-dc-screen` attribute (which React always re-renders WITH), re-declaring
|
|
777
|
+
// the chosen theme's `--*` tokens scoped to the artboard's content wrapper.
|
|
778
|
+
// Survives re-renders, beats the wrapper on cascade order, zero flicker.
|
|
779
|
+
|
|
780
|
+
// Reject token VALUES that could turn the re-emitted <style> into a resource
|
|
781
|
+
// fetch / exfil beacon. Per the CSS spec a custom-property value can't contain
|
|
782
|
+
// an unmatched top-level `}` (so brace-breakout to other selectors is already
|
|
783
|
+
// impossible), but `url()` / `image()` / `@import` / comments survive verbatim.
|
|
784
|
+
// For a TRUSTED same-origin DS this is inert, but a DDR-054 untrusted synced
|
|
785
|
+
// canvas could ship `--bg-0: #fff url(https://attacker/x?leak)` — copying it
|
|
786
|
+
// here would fire that fetch when the artboard renders. Drop such values; the
|
|
787
|
+
// token simply falls back to the DS default for that artboard. (Security
|
|
788
|
+
// review F2, 2026-05-29 — defense-in-depth on top of the canvas-origin CSP.)
|
|
789
|
+
const _UNSAFE_TOKEN_VALUE = /url\(|image\(|image-set\(|-image-set\(|@import|\/\*|expression\(/i;
|
|
790
|
+
|
|
791
|
+
// Collect a DS theme block's custom-property declarations by scanning the
|
|
792
|
+
// loaded stylesheets for top-level rules whose selector targets that theme.
|
|
793
|
+
// Only `--*` props are copied (token re-definitions) — never layout/type, and
|
|
794
|
+
// never @media-nested rules (e.g. the prefers-reduced-motion 1ms collapse).
|
|
795
|
+
const _themeDecls: Partial<Record<'light' | 'dark', string>> = {};
|
|
796
|
+
function collectThemeDeclarations(theme: 'light' | 'dark'): string {
|
|
797
|
+
const cached = _themeDecls[theme];
|
|
798
|
+
if (cached != null) return cached;
|
|
799
|
+
let decls = '';
|
|
800
|
+
if (typeof document !== 'undefined') {
|
|
801
|
+
for (const sheet of Array.from(document.styleSheets)) {
|
|
802
|
+
let rules: CSSRuleList | undefined;
|
|
803
|
+
try {
|
|
804
|
+
rules = sheet.cssRules;
|
|
805
|
+
} catch {
|
|
806
|
+
continue; // cross-origin / unreadable sheet
|
|
807
|
+
}
|
|
808
|
+
if (!rules) continue;
|
|
809
|
+
for (const rule of Array.from(rules)) {
|
|
810
|
+
if (!(rule instanceof CSSStyleRule)) continue;
|
|
811
|
+
if (!(rule.selectorText || '').includes(`[data-theme="${theme}"]`)) continue;
|
|
812
|
+
const style = rule.style;
|
|
813
|
+
for (let i = 0; i < style.length; i++) {
|
|
814
|
+
const prop = style[i];
|
|
815
|
+
if (!prop.startsWith('--')) continue;
|
|
816
|
+
const value = style.getPropertyValue(prop);
|
|
817
|
+
if (_UNSAFE_TOKEN_VALUE.test(value)) continue;
|
|
818
|
+
decls += `${prop}:${value};`;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
// Cache only a NON-EMPTY result — same first-paint race as detectDsThemeSupport:
|
|
824
|
+
// a scan that runs before the DS stylesheet parses finds nothing, and caching
|
|
825
|
+
// '' would freeze the artboard un-themed forever. Leave it uncached so the
|
|
826
|
+
// post-load rebuild (window 'load' / the theme MutationObserver) repopulates it.
|
|
827
|
+
if (decls) _themeDecls[theme] = decls;
|
|
828
|
+
return decls;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// Every artboard FOLLOWS the chrome theme by default (the status-bar toggle is
|
|
832
|
+
// the source of truth). A per-artboard pin OVERRIDES that: 'ds' = the design
|
|
833
|
+
// system's native theme (opt out of following entirely), 'light' / 'dark' = a
|
|
834
|
+
// fixed theme. Absence from this map = follow chrome.
|
|
835
|
+
type ArtboardPin = 'ds' | 'light' | 'dark';
|
|
836
|
+
const _artboardPins = new Map<string, ArtboardPin>();
|
|
837
|
+
|
|
838
|
+
function cssEsc(v: string): string {
|
|
839
|
+
return typeof CSS !== 'undefined' && CSS.escape ? CSS.escape(v) : v;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// Selector targeting the artboard's content wrapper (where the DS theme block
|
|
843
|
+
// is re-established). Beats `.<wrapper>[data-theme="…"]` because the injected
|
|
844
|
+
// <style> is appended after the DS stylesheet (later-wins on equal specificity).
|
|
845
|
+
function artboardScopeSelector(screenId: string): string {
|
|
846
|
+
const { wrapperClass } = detectDsThemeSupport();
|
|
847
|
+
const base = `[data-dc-screen="${cssEsc(screenId)}"]`;
|
|
848
|
+
if (wrapperClass) {
|
|
849
|
+
const c = cssEsc(wrapperClass);
|
|
850
|
+
return `${base} .${c},${base}.${c}`;
|
|
851
|
+
}
|
|
852
|
+
return `${base} [data-theme],${base}[data-theme]`;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// Like artboardScopeSelector but matches EVERY artboard except the pinned ones —
|
|
856
|
+
// backs the default-follow rule, which themes all un-pinned artboards in one
|
|
857
|
+
// declaration. Pinned ids are :not()-excluded so the per-screen pin rule never
|
|
858
|
+
// has to out-specify this global one (they target disjoint elements).
|
|
859
|
+
function globalArtboardScopeSelector(excludeIds: string[]): string {
|
|
860
|
+
const { wrapperClass } = detectDsThemeSupport();
|
|
861
|
+
const not = excludeIds.map((id) => `:not([data-dc-screen="${cssEsc(id)}"])`).join('');
|
|
862
|
+
const base = `[data-dc-screen]${not}`;
|
|
863
|
+
if (wrapperClass) {
|
|
864
|
+
const c = cssEsc(wrapperClass);
|
|
865
|
+
return `${base} .${c},${base}.${c}`;
|
|
866
|
+
}
|
|
867
|
+
return `${base} [data-theme],${base}[data-theme]`;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
function rebuildArtboardThemeStyle(): void {
|
|
871
|
+
if (typeof document === 'undefined') return;
|
|
872
|
+
let el = document.getElementById('dc-artboard-theme-css') as HTMLStyleElement | null;
|
|
873
|
+
if (!el) {
|
|
874
|
+
el = document.createElement('style');
|
|
875
|
+
el.id = 'dc-artboard-theme-css';
|
|
876
|
+
document.head.appendChild(el);
|
|
877
|
+
}
|
|
878
|
+
let css = '';
|
|
879
|
+
// (1) Default — every un-pinned artboard follows the chrome theme. Pinned
|
|
880
|
+
// artboards are :not()-excluded so this rule never competes with their
|
|
881
|
+
// per-screen rule below. When the DS doesn't define the chrome theme's tokens
|
|
882
|
+
// (collectThemeDeclarations → ''), nothing is emitted and the artboard keeps
|
|
883
|
+
// its native DS theme — there's no alternate palette to follow to.
|
|
884
|
+
const pinnedIds = [..._artboardPins.keys()];
|
|
885
|
+
const followDecls = collectThemeDeclarations(currentChromeTheme());
|
|
886
|
+
if (followDecls) css += `${globalArtboardScopeSelector(pinnedIds)}{${followDecls}}\n`;
|
|
887
|
+
// (2) Explicit per-artboard pins. 'ds' emits nothing (the :not() above already
|
|
888
|
+
// released that artboard back to its native DS theme); 'light' / 'dark'
|
|
889
|
+
// re-declare the chosen theme's tokens scoped to that one artboard.
|
|
890
|
+
for (const [screenId, pin] of _artboardPins) {
|
|
891
|
+
if (pin === 'ds') continue;
|
|
892
|
+
const decls = collectThemeDeclarations(pin);
|
|
893
|
+
if (decls) css += `${artboardScopeSelector(screenId)}{${decls}}\n`;
|
|
894
|
+
}
|
|
895
|
+
el.textContent = css;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/** Current canvas-shell chrome theme (mirrors `data-maude-theme` on <html>). */
|
|
899
|
+
function currentChromeTheme(): 'light' | 'dark' {
|
|
900
|
+
if (typeof document === 'undefined') return 'dark';
|
|
901
|
+
return document.documentElement.dataset.maudeTheme === 'light' ? 'light' : 'dark';
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
/**
|
|
905
|
+
* Pin a single artboard's theme (keyed by its stable `data-dc-screen` id), or
|
|
906
|
+
* clear the pin so it returns to the default — following the chrome theme.
|
|
907
|
+
* 'follow' → remove the pin (default: track the chrome theme toggle)
|
|
908
|
+
* 'ds' → pin to the design system's native theme (stop following)
|
|
909
|
+
* 'light' / 'dark' → pin to a fixed theme
|
|
910
|
+
*/
|
|
911
|
+
function setArtboardTheme(screenId: string, mode: 'follow' | ArtboardPin): void {
|
|
912
|
+
if (mode === 'follow') _artboardPins.delete(screenId);
|
|
913
|
+
else _artboardPins.set(screenId, mode);
|
|
914
|
+
rebuildArtboardThemeStyle();
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
/**
|
|
918
|
+
* Re-theme every following artboard when the chrome theme flips. The
|
|
919
|
+
* default-follow rule in rebuildArtboardThemeStyle reads currentChromeTheme()
|
|
920
|
+
* live, so a plain rebuild re-points every un-pinned artboard at the new theme
|
|
921
|
+
* in one shot. Kept under the original name so the MutationObserver call site
|
|
922
|
+
* and the initial 'load' trigger stay untouched.
|
|
923
|
+
*/
|
|
924
|
+
function applyArtboardFollowers(): void {
|
|
925
|
+
rebuildArtboardThemeStyle();
|
|
926
|
+
}
|
|
927
|
+
|
|
662
928
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
663
929
|
// Registry builder — closes over controller + clear callback.
|
|
664
930
|
|
|
@@ -743,6 +1009,57 @@ function buildRegistry(deps: {
|
|
|
743
1009
|
},
|
|
744
1010
|
});
|
|
745
1011
|
|
|
1012
|
+
// Per-artboard theme PIN, keyed by the stable `data-dc-screen` id
|
|
1013
|
+
// (target.artboardId) via an injected stylesheet (see setArtboardTheme).
|
|
1014
|
+
// Following the chrome theme is the default; this submenu overrides it for one
|
|
1015
|
+
// artboard. The DS-supports-both probe gates Light / Dark / "Follow chrome"
|
|
1016
|
+
// (all no-ops on a single-theme DS); only "DS default" is always meaningful.
|
|
1017
|
+
const themeSupport = detectDsThemeSupport();
|
|
1018
|
+
const themeHint = 'This design system defines only one theme';
|
|
1019
|
+
const themeItem: MenuItem = {
|
|
1020
|
+
id: 'theme',
|
|
1021
|
+
label: 'Theme',
|
|
1022
|
+
onSelect: () => {
|
|
1023
|
+
/* parent of a submenu — never invoked directly */
|
|
1024
|
+
},
|
|
1025
|
+
submenu: [
|
|
1026
|
+
{
|
|
1027
|
+
id: 'theme-ds-default',
|
|
1028
|
+
label: 'DS default',
|
|
1029
|
+
onSelect: (target) => {
|
|
1030
|
+
if (target.artboardId) setArtboardTheme(target.artboardId, 'ds');
|
|
1031
|
+
},
|
|
1032
|
+
},
|
|
1033
|
+
{
|
|
1034
|
+
id: 'theme-light',
|
|
1035
|
+
label: 'Light',
|
|
1036
|
+
disabled: !themeSupport.supported,
|
|
1037
|
+
disabledHint: themeHint,
|
|
1038
|
+
onSelect: (target) => {
|
|
1039
|
+
if (target.artboardId) setArtboardTheme(target.artboardId, 'light');
|
|
1040
|
+
},
|
|
1041
|
+
},
|
|
1042
|
+
{
|
|
1043
|
+
id: 'theme-dark',
|
|
1044
|
+
label: 'Dark',
|
|
1045
|
+
disabled: !themeSupport.supported,
|
|
1046
|
+
disabledHint: themeHint,
|
|
1047
|
+
onSelect: (target) => {
|
|
1048
|
+
if (target.artboardId) setArtboardTheme(target.artboardId, 'dark');
|
|
1049
|
+
},
|
|
1050
|
+
},
|
|
1051
|
+
{
|
|
1052
|
+
id: 'theme-follow',
|
|
1053
|
+
label: 'Follow chrome (default)',
|
|
1054
|
+
disabled: !themeSupport.supported,
|
|
1055
|
+
disabledHint: themeHint,
|
|
1056
|
+
onSelect: (target) => {
|
|
1057
|
+
if (target.artboardId) setArtboardTheme(target.artboardId, 'follow');
|
|
1058
|
+
},
|
|
1059
|
+
},
|
|
1060
|
+
],
|
|
1061
|
+
};
|
|
1062
|
+
|
|
746
1063
|
return {
|
|
747
1064
|
element: [
|
|
748
1065
|
[
|
|
@@ -868,6 +1185,7 @@ function buildRegistry(deps: {
|
|
|
868
1185
|
onSelect: () => distributeArtboards('y'),
|
|
869
1186
|
},
|
|
870
1187
|
],
|
|
1188
|
+
[themeItem],
|
|
871
1189
|
[exportItem('export-artboard', 'Export this artboard…', 'artboard')],
|
|
872
1190
|
],
|
|
873
1191
|
world: [
|
|
@@ -944,6 +1262,13 @@ function CanvasRouter({
|
|
|
944
1262
|
// user closes the composer.
|
|
945
1263
|
useEffect(() => {
|
|
946
1264
|
if (typeof window === 'undefined') return;
|
|
1265
|
+
// Default the canvas-shell chrome theme to dark (matches the dev-server's
|
|
1266
|
+
// own default + the bare `:root` --maude-chrome-* set) so a canvas that
|
|
1267
|
+
// never receives a `dgn:'theme'` message still renders coherent-dark and
|
|
1268
|
+
// the follow-chrome observer (Task 5) has a concrete value to mirror.
|
|
1269
|
+
if (!document.documentElement.dataset.maudeTheme) {
|
|
1270
|
+
document.documentElement.dataset.maudeTheme = 'dark';
|
|
1271
|
+
}
|
|
947
1272
|
const onMessage = (e: MessageEvent) => {
|
|
948
1273
|
const m = e.data as { dgn?: string } | null;
|
|
949
1274
|
if (!m || typeof m !== 'object' || !m.dgn) return;
|
|
@@ -958,6 +1283,19 @@ function CanvasRouter({
|
|
|
958
1283
|
if (typeof t === 'string') setTool(t as never);
|
|
959
1284
|
return;
|
|
960
1285
|
}
|
|
1286
|
+
// D9 — canvas-shell chrome follows the Maude chrome theme. The chrome's
|
|
1287
|
+
// `--maude-chrome-*` token family is keyed by `data-maude-theme` on the
|
|
1288
|
+
// iframe documentElement (see HUD_TOKENS_CSS). This attribute is
|
|
1289
|
+
// DELIBERATELY separate from the DS `data-theme`: it only re-themes the
|
|
1290
|
+
// floating chrome, never the artboard palettes. Followers (per-artboard
|
|
1291
|
+
// "Follow chrome") restamp via the MutationObserver in CanvasShell.
|
|
1292
|
+
if (m.dgn === 'theme') {
|
|
1293
|
+
const t = (m as { theme?: string }).theme;
|
|
1294
|
+
if (t === 'light' || t === 'dark') {
|
|
1295
|
+
document.documentElement.dataset.maudeTheme = t;
|
|
1296
|
+
}
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
961
1299
|
};
|
|
962
1300
|
window.addEventListener('message', onMessage);
|
|
963
1301
|
return () => window.removeEventListener('message', onMessage);
|
|
@@ -1085,14 +1423,14 @@ const MULTI_TOOLBAR_CSS = `
|
|
|
1085
1423
|
align-items: stretch;
|
|
1086
1424
|
gap: 2px;
|
|
1087
1425
|
padding: 4px;
|
|
1088
|
-
background: var(--
|
|
1089
|
-
border: 1px solid var(--
|
|
1426
|
+
background: var(--maude-chrome-bg-0, #ffffff);
|
|
1427
|
+
border: 1px solid var(--maude-chrome-fg-0, #1c1917);
|
|
1090
1428
|
border-radius: 8px;
|
|
1091
|
-
box-shadow: 0 6px 24px color-mix(in oklab,
|
|
1092
|
-
font-family: var(--
|
|
1429
|
+
box-shadow: 0 6px 24px var(--maude-chrome-shadow, color-mix(in oklab, #1c1917 10%, transparent));
|
|
1430
|
+
font-family: var(--maude-chrome-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
|
|
1093
1431
|
font-size: 11px;
|
|
1094
1432
|
letter-spacing: 0.02em;
|
|
1095
|
-
color: var(--
|
|
1433
|
+
color: var(--maude-chrome-fg-0, #1a1a1a);
|
|
1096
1434
|
user-select: none;
|
|
1097
1435
|
}
|
|
1098
1436
|
.dc-multi-artboard-tb button {
|
|
@@ -1118,8 +1456,8 @@ const MULTI_TOOLBAR_CSS = `
|
|
|
1118
1456
|
}
|
|
1119
1457
|
.dc-multi-artboard-tb .dc-mab-count {
|
|
1120
1458
|
padding: 4px 8px 4px 10px;
|
|
1121
|
-
color: var(--fg-1, rgba(40,30,20,0.7));
|
|
1122
|
-
border-right: 1px solid var(--
|
|
1459
|
+
color: var(--maude-chrome-fg-1, rgba(40,30,20,0.7));
|
|
1460
|
+
border-right: 1px solid var(--maude-chrome-border, rgba(0,0,0,0.08));
|
|
1123
1461
|
margin-right: 2px;
|
|
1124
1462
|
font-variant-numeric: tabular-nums;
|
|
1125
1463
|
}
|
|
@@ -1131,7 +1469,7 @@ const MULTI_TOOLBAR_CSS = `
|
|
|
1131
1469
|
.dc-multi-artboard-tb .dc-mab-divider {
|
|
1132
1470
|
width: 1px;
|
|
1133
1471
|
align-self: stretch;
|
|
1134
|
-
background: var(--
|
|
1472
|
+
background: var(--maude-chrome-border, rgba(0,0,0,0.10));
|
|
1135
1473
|
margin: 0 4px;
|
|
1136
1474
|
}
|
|
1137
1475
|
@media (prefers-reduced-motion: reduce) {
|