@1agh/maude 0.45.2 → 0.47.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 (91) hide show
  1. package/apps/studio/acp/bootstrap-brief.ts +8 -0
  2. package/apps/studio/acp/bridge.ts +151 -1
  3. package/apps/studio/annotations-layer.tsx +42 -0
  4. package/apps/studio/annotations-snap.ts +50 -10
  5. package/apps/studio/api.ts +760 -15
  6. package/apps/studio/artboard-guides-overlay.tsx +270 -0
  7. package/apps/studio/bin/_agent-browser-safe-config.json +1 -0
  8. package/apps/studio/bin/_agent-browser-safe.mjs +228 -0
  9. package/apps/studio/bin/_agent-browser-safe.test.mjs +165 -0
  10. package/apps/studio/bin/_curl-local.mjs +349 -0
  11. package/apps/studio/bin/_curl-local.test.mjs +280 -0
  12. package/apps/studio/bin/_pdf-playwright.mjs +35 -4
  13. package/apps/studio/bin/_png-playwright.mjs +38 -4
  14. package/apps/studio/bin/_pw-launch.mjs +52 -0
  15. package/apps/studio/bin/_pw-launch.test.mjs +90 -0
  16. package/apps/studio/bin/_smart-frames.mjs +419 -0
  17. package/apps/studio/bin/_smart-frames.test.mjs +140 -0
  18. package/apps/studio/bin/agent-browser-safe.sh +29 -0
  19. package/apps/studio/bin/curl-local.sh +28 -0
  20. package/apps/studio/bin/smart-frames.sh +30 -0
  21. package/apps/studio/canvas-cursors.ts +6 -0
  22. package/apps/studio/canvas-edit.ts +972 -6
  23. package/apps/studio/canvas-icons.tsx +13 -0
  24. package/apps/studio/canvas-lib.tsx +206 -11
  25. package/apps/studio/canvas-shell.tsx +809 -50
  26. package/apps/studio/client/app.jsx +1554 -206
  27. package/apps/studio/client/panels/ChatPanel.jsx +92 -3
  28. package/apps/studio/client/panels/SettingsPanel.jsx +211 -0
  29. package/apps/studio/client/panels/acp-capabilities.js +11 -0
  30. package/apps/studio/client/styles/3-shell-maude.css +39 -0
  31. package/apps/studio/client/styles/6-acp-chat.css +41 -0
  32. package/apps/studio/contextual-toolbar.tsx +5 -3
  33. package/apps/studio/dist/client.bundle.js +1627 -1627
  34. package/apps/studio/dist/comment-mount.js +2 -2
  35. package/apps/studio/dist/styles.css +1 -1
  36. package/apps/studio/dom-selection.ts +20 -0
  37. package/apps/studio/export-dialog.tsx +138 -18
  38. package/apps/studio/exporters/pdf.ts +332 -18
  39. package/apps/studio/exporters/png.ts +45 -3
  40. package/apps/studio/footage/schema.ts +17 -1
  41. package/apps/studio/generation/gemma-models.ts +224 -0
  42. package/apps/studio/generation/prefs.ts +43 -0
  43. package/apps/studio/grid-track-handles.ts +179 -0
  44. package/apps/studio/handoff.ts +35 -0
  45. package/apps/studio/http.ts +331 -7
  46. package/apps/studio/input-router.tsx +73 -17
  47. package/apps/studio/print/marks.ts +113 -0
  48. package/apps/studio/print/units.ts +269 -0
  49. package/apps/studio/print-overlay-content.tsx +132 -0
  50. package/apps/studio/test/acp-mode-banner.test.ts +45 -0
  51. package/apps/studio/test/acp-session-allowed-tools.test.ts +187 -0
  52. package/apps/studio/test/annotations-snap.test.ts +56 -0
  53. package/apps/studio/test/artboard-guides-overlay.test.tsx +152 -0
  54. package/apps/studio/test/artboard-kinds.test.tsx +83 -0
  55. package/apps/studio/test/artboard-selection-attrs.test.ts +69 -0
  56. package/apps/studio/test/browse-posture.test.tsx +107 -0
  57. package/apps/studio/test/canvas-hide-chrome.test.ts +58 -0
  58. package/apps/studio/test/canvas-meta-api.test.ts +237 -0
  59. package/apps/studio/test/canvas-origin-gate.test.ts +4 -0
  60. package/apps/studio/test/comment-mount.test.ts +2 -1
  61. package/apps/studio/test/component-map.test.ts +48 -0
  62. package/apps/studio/test/convert-to-absolute.test.ts +333 -0
  63. package/apps/studio/test/detach-component.test.ts +94 -0
  64. package/apps/studio/test/edit-scope-api.test.ts +8 -4
  65. package/apps/studio/test/element-structural-api.test.ts +74 -0
  66. package/apps/studio/test/element-structural-edit.test.ts +363 -0
  67. package/apps/studio/test/exporters/png.test.ts +49 -1
  68. package/apps/studio/test/grid-track-handles.test.ts +160 -0
  69. package/apps/studio/test/handoff.test.ts +48 -0
  70. package/apps/studio/test/input-router.test.ts +82 -8
  71. package/apps/studio/test/layers-synthetic-groups.test.ts +96 -0
  72. package/apps/studio/test/pdf-print-boxes.test.ts +326 -0
  73. package/apps/studio/test/print-marks.test.ts +113 -0
  74. package/apps/studio/test/print-units.test.ts +173 -0
  75. package/apps/studio/test/use-snap-guides.test.ts +81 -0
  76. package/apps/studio/test/use-tool-mode.test.tsx +10 -2
  77. package/apps/studio/tool-palette.tsx +3 -1
  78. package/apps/studio/use-canvas-media-drop.tsx +126 -0
  79. package/apps/studio/use-chrome-visibility.tsx +19 -0
  80. package/apps/studio/use-element-resize.tsx +24 -3
  81. package/apps/studio/use-grid-track-handles.tsx +364 -0
  82. package/apps/studio/use-keyboard-discipline.tsx +15 -0
  83. package/apps/studio/use-snap-guides.tsx +73 -5
  84. package/apps/studio/use-spacing-handles.tsx +9 -5
  85. package/apps/studio/use-tool-mode.tsx +30 -3
  86. package/apps/studio/web-overlay-content.tsx +52 -0
  87. package/apps/studio/whats-new.json +55 -0
  88. package/cli/commands/design.mjs +25 -1
  89. package/package.json +8 -8
  90. package/plugins/design/dependencies.json +35 -0
  91. package/plugins/design/templates/_shell.html +4 -0
@@ -39,6 +39,18 @@ export function IconMove(props: IconProps) {
39
39
  </Svg>
40
40
  );
41
41
  }
42
+ // feature-4 (browse/move split) — Browse tool. A pointing-hand ("tap") cursor:
43
+ // the universal "the mock is alive, click it" glyph, deliberately distinct from
44
+ // Move's solid selection arrow.
45
+ export function IconBrowse(props: IconProps) {
46
+ return (
47
+ <Svg {...props}>
48
+ <path d="M10 9V5a1.4 1.4 0 012.8 0v6" />
49
+ <path d="M12.8 10.4V9.6a1.3 1.3 0 012.6 0V12" />
50
+ <path d="M15.4 12.2v-1a1.3 1.3 0 012.6 0V15c0 2.6-1.7 5-5 5-2 0-3.3-.8-4.4-2.2L6 13.4a1.4 1.4 0 012.2-1.7L10 13.6V9" />
51
+ </Svg>
52
+ );
53
+ }
42
54
 
43
55
  export function IconHand(props: IconProps) {
44
56
  return (
@@ -463,6 +475,7 @@ export function IconHighlighter(props: IconProps) {
463
475
  }
464
476
 
465
477
  export const TOOL_ICONS: Record<string, (p: IconProps) => JSX.Element> = {
478
+ browse: IconBrowse,
466
479
  move: IconMove,
467
480
  hand: IconHand,
468
481
  comment: IconComment,
@@ -101,7 +101,23 @@ import {
101
101
  } from 'react';
102
102
 
103
103
  import { ArtboardActivityOverlay } from './artboard-activity-overlay.tsx';
104
+ import {
105
+ ArtboardGuidesOverlay,
106
+ type GuideDefinitions,
107
+ type OverlayVisibility,
108
+ } from './artboard-guides-overlay.tsx';
104
109
  import { CanvasShell } from './canvas-shell.tsx';
110
+ import type { ArtboardPrintProp } from './print/units.ts';
111
+ // feature-2-print-artboards T3 — side-effect import: registers the print
112
+ // kind's overlay content (bleed/trim/margin) into artboard-guides-overlay's
113
+ // registry at module load, per that file's own "call registerKindOverlay at
114
+ // module load" contract. Must live in the same module graph every canvas
115
+ // bundle already pulls in (canvas-lib IS that shared graph — DDR-025), so a
116
+ // canvas never renders without the registration having run first.
117
+ import './print-overlay-content.tsx';
118
+ // feature-3-web-artboards T2 — same side-effect-import contract as the print
119
+ // registration above, for the 'web' kind's breakpoint-band chip.
120
+ import './web-overlay-content.tsx';
105
121
  import {
106
122
  buildMoveArtboardsRecord,
107
123
  diffLayoutPositions,
@@ -299,6 +315,21 @@ const ENGINE_CSS = `
299
315
  color: var(--fg-1, #4a3f30);
300
316
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
301
317
  }
318
+ /* T3 — per-kind chrome. Subtle tint on the header + a small glyph before the
319
+ label text, for any non-digital kind. digital (the default, and every
320
+ unmigrated canvas) renders no chip and no tint — byte-identical chrome. */
321
+ .dc-canvas .dc-artboard[data-dc-kind]:not([data-dc-kind='digital']) .dc-artboard-label {
322
+ background: color-mix(in oklab, var(--accent, var(--fg-2, #4a3f30)) 10%, var(--bg-2, #e8e3d8));
323
+ }
324
+ .dc-canvas .dc-artboard-kind-chip {
325
+ display: inline-flex;
326
+ align-items: center;
327
+ justify-content: center;
328
+ margin-right: 6px;
329
+ color: var(--accent, var(--fg-1, #4a3f30));
330
+ opacity: 0.85;
331
+ vertical-align: -1px;
332
+ }
302
333
  .dc-canvas .dc-artboard-body {
303
334
  flex: 1;
304
335
  position: relative;
@@ -379,12 +410,25 @@ function ensureEngineStyles(): void {
379
410
  // world-coord positioning) and by DCSection (which collapses inside the
380
411
  // canvas) and by future T3 components (MiniMap, ZoomToolbar).
381
412
 
413
+ /**
414
+ * What an artboard IS — digital screen / print page / web flow / video comp.
415
+ * Absent JSX prop resolves to `digital` (DDR-027-consistent: JSX is truth,
416
+ * no meta PATCH lane). Drives chrome (T3), the guides overlay content
417
+ * registry (T4), and generation/editing rules downstream (feature-2-print,
418
+ * feature-3-web).
419
+ */
420
+ export type ArtboardKind = 'digital' | 'print' | 'web' | 'video';
421
+
382
422
  export interface ArtboardRect {
383
423
  id: string;
384
424
  x: number;
385
425
  y: number;
386
426
  w: number;
387
427
  h: number;
428
+ /** Resolved kind (explicit `kind` prop, or `video` fallback via
429
+ * `subtreeHasVideoComp`, or `digital`) — JSX-authoritative, recomputed
430
+ * from `harvestArtboards` every render; never persisted to meta. */
431
+ kind: ArtboardKind;
388
432
  }
389
433
 
390
434
  export interface ViewportState {
@@ -445,8 +489,11 @@ interface ArtboardSeed {
445
489
  id: string;
446
490
  w: number;
447
491
  h: number;
492
+ kind: ArtboardKind;
448
493
  }
449
494
 
495
+ const ARTBOARD_KINDS: ReadonlySet<string> = new Set(['digital', 'print', 'web', 'video']);
496
+
450
497
  /**
451
498
  * Walk a children subtree harvesting DCArtboard descriptors in render order.
452
499
  * DCSection (and any other wrapper) is traversed but doesn't appear in the
@@ -469,11 +516,22 @@ function harvestArtboards(children: ReactNode): ArtboardSeed[] {
469
516
  (typeof type === 'function' &&
470
517
  (type as { displayName?: string }).displayName === 'DCArtboard');
471
518
  if (isArtboard) {
472
- const props = node.props as { id?: string; width?: number; height?: number };
519
+ const props = node.props as {
520
+ id?: string;
521
+ width?: number;
522
+ height?: number;
523
+ kind?: string;
524
+ children?: ReactNode;
525
+ };
526
+ const explicitKind =
527
+ typeof props.kind === 'string' && ARTBOARD_KINDS.has(props.kind)
528
+ ? (props.kind as ArtboardKind)
529
+ : undefined;
473
530
  out.push({
474
531
  id: typeof props.id === 'string' && props.id.length > 0 ? props.id : `__ab_${auto}`,
475
532
  w: typeof props.width === 'number' ? props.width : VP_GRID.w,
476
533
  h: typeof props.height === 'number' ? props.height : VP_GRID.h,
534
+ kind: explicitKind ?? (subtreeHasVideoComp(props.children) ? 'video' : 'digital'),
477
535
  });
478
536
  auto++;
479
537
  return;
@@ -529,6 +587,7 @@ function synthDefaultGrid(seeds: ArtboardSeed[]): ArtboardRect[] {
529
587
  y: row * (cellH + VP_GRID.gutter),
530
588
  w: seed.w,
531
589
  h: seed.h,
590
+ kind: seed.kind,
532
591
  };
533
592
  });
534
593
  }
@@ -565,6 +624,9 @@ function readCanvasMeta():
565
624
  | {
566
625
  layout?: { artboards?: ArtboardRect[] };
567
626
  viewport?: ViewportState;
627
+ /** T6 — per-user overlay-visibility bag, GET-merged in from view.json
628
+ * server-side (never present in the versioned `.meta.json` itself). */
629
+ overlays?: OverlayVisibility;
568
630
  }
569
631
  | undefined {
570
632
  if (typeof window === 'undefined') return undefined;
@@ -572,6 +634,7 @@ function readCanvasMeta():
572
634
  __canvas_meta__?: {
573
635
  layout?: { artboards?: ArtboardRect[] };
574
636
  viewport?: ViewportState;
637
+ overlays?: OverlayVisibility;
575
638
  };
576
639
  };
577
640
  return w.__canvas_meta__;
@@ -1487,6 +1550,21 @@ function DesignCanvasInner({ children, controls }: DesignCanvasProps) {
1487
1550
  const hostRef = useRef<HTMLDivElement | null>(null);
1488
1551
  const worldRef = useRef<HTMLDivElement | null>(null);
1489
1552
 
1553
+ // T6 — self-seed the (per-canvas, persisted) guides visibility into the
1554
+ // shared ChromeVisibilityContext once at mount, from the SAME
1555
+ // window.__canvas_meta__ snapshot the viewport/layout reads above use.
1556
+ // Unlike minimap/zoom/present (global shell prefs pushed in via postMessage
1557
+ // shortly after load), `guides` has no global default to race — it's
1558
+ // read directly out of this canvas's own meta, so there's nothing to wait
1559
+ // for. The shell's View-menu toggle then live-updates it through the same
1560
+ // `dgn:'view-chrome'` channel as the other three flags.
1561
+ // T6 — the per-canvas overlay-visibility self-seed used to live here, but
1562
+ // this component renders CanvasShell as its CHILD and the
1563
+ // ChromeVisibilityProvider mounts INSIDE CanvasShell — so this component's
1564
+ // own useChromeVisibility() was always null and the seed silently never
1565
+ // ran (dead code since the foundation feature). The working seed now lives
1566
+ // in canvas-shell.tsx's CanvasRouter, which sits inside the provider.
1567
+
1490
1568
  const seeds = useMemo(() => harvestArtboards(children), [children]);
1491
1569
 
1492
1570
  // Merge JSX-derived defaults with meta-persisted positions. Per DDR-027,
@@ -1511,6 +1589,10 @@ function DesignCanvasInner({ children, controls }: DesignCanvasProps) {
1511
1589
  y: Number.isFinite(m.y) ? m.y : d.y,
1512
1590
  w: typeof m.w === 'number' && m.w > 0 ? m.w : d.w,
1513
1591
  h: typeof m.h === 'number' && m.h > 0 ? m.h : d.h,
1592
+ // kind is JSX-authoritative like w/h (DDR-027) but, unlike w/h, was
1593
+ // never part of the legacy persisted layout shape — always the fresh
1594
+ // seed value, no meta fallback to tolerate.
1595
+ kind: d.kind,
1514
1596
  };
1515
1597
  });
1516
1598
  }, [seeds]);
@@ -1803,6 +1885,57 @@ export function DCSection({
1803
1885
  }
1804
1886
  DCSection.displayName = 'DCSection';
1805
1887
 
1888
+ /**
1889
+ * Small monochrome glyph shown in the SKU-strip header for a non-`digital`
1890
+ * artboard (T3). Mirrors the corner video-badge's stroke style (viewBox 16,
1891
+ * `currentColor`, ~1.2px stroke) for chrome consistency. `video` reuses that
1892
+ * exact badge glyph.
1893
+ */
1894
+ function ArtboardKindIcon({ kind }: { kind: ArtboardKind }) {
1895
+ if (kind === 'print') {
1896
+ return (
1897
+ <svg viewBox="0 0 16 16" width="10" height="10" fill="none" aria-hidden="true">
1898
+ <rect
1899
+ x="3"
1900
+ y="1.5"
1901
+ width="10"
1902
+ height="5"
1903
+ rx="0.5"
1904
+ stroke="currentColor"
1905
+ strokeWidth="1.2"
1906
+ />
1907
+ <rect x="1.5" y="6" width="13" height="6" rx="1" stroke="currentColor" strokeWidth="1.2" />
1908
+ <rect x="4.5" y="10.5" width="7" height="4" stroke="currentColor" strokeWidth="1.1" />
1909
+ </svg>
1910
+ );
1911
+ }
1912
+ if (kind === 'web') {
1913
+ return (
1914
+ <svg viewBox="0 0 16 16" width="10" height="10" fill="none" aria-hidden="true">
1915
+ <circle cx="8" cy="8" r="6" stroke="currentColor" strokeWidth="1.2" />
1916
+ <path
1917
+ d="M2 8h12M8 2c2 2 2 10 0 12M8 2c-2 2-2 10 0 12"
1918
+ stroke="currentColor"
1919
+ strokeWidth="1.1"
1920
+ />
1921
+ </svg>
1922
+ );
1923
+ }
1924
+ // video — same glyph as the corner Timeline badge.
1925
+ return (
1926
+ <svg viewBox="0 0 16 16" width="10" height="10" fill="none" aria-hidden="true">
1927
+ <rect x="1.5" y="4.5" width="8" height="7" rx="1.2" stroke="currentColor" strokeWidth="1.3" />
1928
+ <path
1929
+ d="M9.5 7l4-2.3v6.6l-4-2.3"
1930
+ stroke="currentColor"
1931
+ strokeWidth="1.3"
1932
+ strokeLinejoin="round"
1933
+ />
1934
+ </svg>
1935
+ );
1936
+ }
1937
+ ArtboardKindIcon.displayName = 'ArtboardKindIcon';
1938
+
1806
1939
  /**
1807
1940
  * Bordered artboard with a SKU-strip header. Inside DesignCanvas its world
1808
1941
  * position comes from meta.layout (or the synthesized default grid); the
@@ -1820,6 +1953,9 @@ export function DCArtboard({
1820
1953
  padding,
1821
1954
  layout,
1822
1955
  gap,
1956
+ kind,
1957
+ guides,
1958
+ print,
1823
1959
  children,
1824
1960
  }: {
1825
1961
  id: string;
@@ -1833,17 +1969,33 @@ export function DCArtboard({
1833
1969
  fixed?: boolean;
1834
1970
  /** `.dc-artboard-body` background (CSS color/token, e.g. "var(--bg-1)"). */
1835
1971
  background?: string;
1836
- /** `.dc-artboard-body` padding, px, all sides. */
1837
- padding?: number;
1972
+ /** `.dc-artboard-body` padding — px number, or a `var(--token)` string
1973
+ * (space-token binding from the Inspector's artboard panel). */
1974
+ padding?: number | string;
1838
1975
  /** `.dc-artboard-body` layout mode. Default = the engine's plain block flow. */
1839
1976
  layout?: 'block' | 'flex-col' | 'flex-row' | 'grid';
1840
- /** `.dc-artboard-body` gap, px — only visible under flex-col/flex-row/grid. */
1841
- gap?: number;
1977
+ /** `.dc-artboard-body` gap — px number or `var(--token)` string; only
1978
+ * visible under flex-col/flex-row/grid. */
1979
+ gap?: number | string;
1980
+ /** What this artboard IS — digital screen / print page / web flow / video
1981
+ * comp. Absent ⇒ `digital` (or `video` when the subtree contains a
1982
+ * `<VideoComp>`, for unmigrated canvases — see `subtreeHasVideoComp`).
1983
+ * An explicit prop always supersedes that structural fallback. */
1984
+ kind?: ArtboardKind;
1985
+ /** Generic layout guides (T5) — Figma-vocabulary columns/rows/grid,
1986
+ * versioned design intent. Kind-agnostic: any artboard can carry these
1987
+ * regardless of `kind`. Visibility is per-user (T6), not this prop. */
1988
+ guides?: GuideDefinitions;
1989
+ /** feature-2-print-artboards T2 — paper/orientation/bleed/margins intent.
1990
+ * Only meaningful when `kind="print"`; the overlay (T3) reads it to draw
1991
+ * bleed/trim/margin guides against THIS artboard's own resolved px size. */
1992
+ print?: ArtboardPrintProp;
1842
1993
  children: ReactNode;
1843
1994
  }) {
1844
1995
  const ctx = useWorldContext();
1845
1996
  const _controller = useViewportControllerContext();
1846
1997
  const toolMode = useToolModeOptional();
1998
+ const chrome = useChromeVisibility();
1847
1999
  const selSet = useSelectionSetOptional();
1848
2000
  const dragBus = useDragStateContext();
1849
2001
  // Phase 13 / DDR-029 — live "agent works here" overlay. Inert (present=false)
@@ -1853,10 +2005,15 @@ export function DCArtboard({
1853
2005
  // its presence color + show its funny name instead of the generic file label.
1854
2006
  const agent = useAgentPresence();
1855
2007
  const rect = ctx ? ctx.rectFor(id) : null;
2008
+ // T1/T2 — explicit `kind` wins; `subtreeHasVideoComp` is the fallback that
2009
+ // keeps existing unmigrated video canvases badged identically without
2010
+ // requiring a JSX edit (Design Decision 1).
2011
+ const hasVideoContent = useMemo(() => subtreeHasVideoComp(children), [children]);
2012
+ const resolvedKind: ArtboardKind = kind ?? (hasVideoContent ? 'video' : 'digital');
1856
2013
  // DDR-148 — badge video artboards in the header; clicking the badge opens
1857
2014
  // the timeline panel via the same postMessage the context-menu "Open
1858
2015
  // Timeline" entry already sends (canvas-shell.tsx artboardHasVideo/handler).
1859
- const hasVideo = useMemo(() => subtreeHasVideoComp(children), [children]);
2016
+ const hasVideo = resolvedKind === 'video';
1860
2017
  const openTimeline = useCallback(
1861
2018
  (e: ReactMouseEvent<HTMLButtonElement>) => {
1862
2019
  e.stopPropagation();
@@ -1921,8 +2078,9 @@ export function DCArtboard({
1921
2078
  const bodyStyle = useMemo<CSSProperties>(() => {
1922
2079
  const st: CSSProperties = {};
1923
2080
  if (background) st.background = background;
1924
- if (typeof padding === 'number') st.padding = padding;
1925
- if (typeof gap === 'number') st.gap = gap;
2081
+ // number → px; string → a var(--token) binding (validated server-side).
2082
+ if (typeof padding === 'number' || typeof padding === 'string') st.padding = padding;
2083
+ if (typeof gap === 'number' || typeof gap === 'string') st.gap = gap;
1926
2084
  if (layout === 'flex-col') {
1927
2085
  st.display = 'flex';
1928
2086
  st.flexDirection = 'column';
@@ -1943,9 +2101,20 @@ export function DCArtboard({
1943
2101
  const readBackAttrs = {
1944
2102
  'data-dc-fixed': fixed ? 'true' : undefined,
1945
2103
  'data-dc-bg': background,
1946
- 'data-dc-padding': typeof padding === 'number' ? String(padding) : undefined,
2104
+ 'data-dc-padding': padding != null ? String(padding) : undefined,
1947
2105
  'data-dc-layout': layout,
1948
- 'data-dc-gap': typeof gap === 'number' ? String(gap) : undefined,
2106
+ 'data-dc-gap': gap != null ? String(gap) : undefined,
2107
+ // Always present (unlike the optional-override props above) — this is a
2108
+ // resolved classification, not a "no override" style knob. Chrome, the
2109
+ // Inspector kind picker (T8), and canvas-shell's artboardHasVideo gate
2110
+ // (T2) all key off this attribute.
2111
+ 'data-dc-kind': resolvedKind,
2112
+ // feature-2-print-artboards T2 — read-back for the Inspector's print
2113
+ // picker (paper/orientation/bleed pre-fill), same "stamp the resolved
2114
+ // prop as JSON, parse it back" shape `guides` would need if it grew an
2115
+ // Inspector editor. Small object (≤ ~150 bytes), so a plain JSON attr is
2116
+ // simpler than adding parallel data-dc-print-* scalar attrs per field.
2117
+ 'data-dc-print': print ? JSON.stringify(print) : undefined,
1949
2118
  };
1950
2119
 
1951
2120
  if (!ctx || !rect) {
@@ -2037,7 +2206,22 @@ export function DCArtboard({
2037
2206
  }}
2038
2207
  {...handleProps}
2039
2208
  >
2040
- <button type="button" className="dc-artboard-label sku" aria-label={`Artboard ${label}`}>
2209
+ <button
2210
+ type="button"
2211
+ className="dc-artboard-label sku"
2212
+ // a11y-auditor (T3 review) — the kind chip is aria-hidden (decorative,
2213
+ // redundant with the Inspector's Kind picker), so a non-digital kind
2214
+ // must still reach the artboard's own accessible name or it's
2215
+ // invisible to AT users entirely.
2216
+ aria-label={
2217
+ resolvedKind !== 'digital' ? `Artboard ${label}, ${resolvedKind}` : `Artboard ${label}`
2218
+ }
2219
+ >
2220
+ {resolvedKind !== 'digital' ? (
2221
+ <span className="dc-artboard-kind-chip" aria-hidden="true">
2222
+ <ArtboardKindIcon kind={resolvedKind} />
2223
+ </span>
2224
+ ) : null}
2041
2225
  {label}
2042
2226
  </button>
2043
2227
  {hasVideo ? (
@@ -2079,6 +2263,17 @@ export function DCArtboard({
2079
2263
  color={agent?.color}
2080
2264
  />
2081
2265
  ) : null}
2266
+ {/* T4 — world-coord sibling, same reasoning as ArtboardActivityOverlay
2267
+ above: must stay OUTSIDE `.dc-artboard`'s contain:paint/
2268
+ content-visibility subtree so guides are never culled/frozen with
2269
+ exported content (they're never exported at all). */}
2270
+ <ArtboardGuidesOverlay
2271
+ rect={{ x: liveX, y: liveY, w: rect.w, h: rect.h }}
2272
+ kind={resolvedKind}
2273
+ guides={guides}
2274
+ print={print}
2275
+ visibility={{ guides: chrome?.guides ?? false, print: chrome?.print ?? false }}
2276
+ />
2082
2277
  </>
2083
2278
  );
2084
2279
  }