@1agh/maude 0.39.1 → 0.41.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 +1 -1
- package/apps/studio/ai-banner.tsx +2 -2
- package/apps/studio/annotations-context-toolbar.tsx +44 -1
- package/apps/studio/annotations-layer.tsx +306 -5
- package/apps/studio/annotations-model.ts +107 -6
- package/apps/studio/api.ts +1316 -67
- package/apps/studio/artboard-marquee.tsx +1 -1
- package/apps/studio/bin/_canvas-rects-playwright.mjs +55 -0
- package/apps/studio/bin/_canvas-rects-static.mjs +166 -0
- package/apps/studio/bin/_fetch-asset.mjs +556 -0
- package/apps/studio/bin/_html-playwright.mjs +9 -1
- package/apps/studio/bin/_pdf-playwright.mjs +8 -1
- package/apps/studio/bin/_png-playwright.mjs +8 -1
- package/apps/studio/bin/_pw-launch.mjs +54 -0
- package/apps/studio/bin/_svg-playwright.mjs +8 -1
- package/apps/studio/bin/_video-playwright.mjs +452 -0
- package/apps/studio/bin/annotate.mjs +576 -34
- package/apps/studio/bin/canvas-rects.sh +152 -0
- package/apps/studio/bin/fetch-asset.sh +34 -0
- package/apps/studio/bin/prep.sh +8 -1
- package/apps/studio/bin/read-annotations.mjs +138 -7
- package/apps/studio/bin/smoke.sh +42 -6
- package/apps/studio/build.ts +21 -0
- package/apps/studio/canvas-comment-mount.tsx +138 -4
- package/apps/studio/canvas-edit.ts +2625 -111
- package/apps/studio/canvas-lib.tsx +238 -2
- package/apps/studio/canvas-list-watch.ts +6 -2
- package/apps/studio/canvas-shell.tsx +514 -20
- package/apps/studio/client/app.jsx +2480 -52
- package/apps/studio/client/comments-overlay.css +130 -126
- package/apps/studio/client/github.js +15 -0
- package/apps/studio/client/panels/TimelinePanel.jsx +860 -0
- package/apps/studio/client/panels/timeline-parse.js +229 -0
- package/apps/studio/client/panels/timeline-snap.js +55 -0
- package/apps/studio/client/styles/3-shell-maude.css +155 -0
- package/apps/studio/comments-overlay.tsx +148 -41
- package/apps/studio/config.schema.json +14 -0
- package/apps/studio/context-menu.tsx +16 -6
- package/apps/studio/context.ts +113 -1
- package/apps/studio/contextual-toolbar.tsx +262 -4
- package/apps/studio/cursors-overlay.tsx +4 -4
- package/apps/studio/dist/client.bundle.js +89 -17
- package/apps/studio/dist/comment-mount.js +59 -1
- package/apps/studio/dist/runtime/.min-sizes.json +11 -1
- package/apps/studio/dist/runtime/@remotion_media.js +491 -0
- package/apps/studio/dist/runtime/@remotion_player.js +56 -0
- package/apps/studio/dist/runtime/@remotion_transitions.js +300 -0
- package/apps/studio/dist/runtime/@remotion_transitions_clock-wipe.js +1 -0
- package/apps/studio/dist/runtime/@remotion_transitions_fade.js +1 -0
- package/apps/studio/dist/runtime/@remotion_transitions_flip.js +1 -0
- package/apps/studio/dist/runtime/@remotion_transitions_none.js +1 -0
- package/apps/studio/dist/runtime/@remotion_transitions_slide.js +1 -0
- package/apps/studio/dist/runtime/@remotion_transitions_wipe.js +1 -0
- package/apps/studio/dist/runtime/REMOTION-LICENSE.md +28 -0
- package/apps/studio/dist/runtime/remotion.js +42 -0
- package/apps/studio/dist/styles.css +1 -1
- package/apps/studio/dom-selection.ts +127 -1
- package/apps/studio/drag-state.ts +24 -0
- package/apps/studio/equal-spacing-detector.ts +205 -0
- package/apps/studio/export-dialog.tsx +1 -1
- package/apps/studio/exporters/_browser-bundles.ts +117 -0
- package/apps/studio/exporters/_runtime.ts +69 -0
- package/apps/studio/exporters/html.ts +4 -3
- package/apps/studio/exporters/index.ts +28 -2
- package/apps/studio/exporters/pdf.ts +4 -3
- package/apps/studio/exporters/png.ts +5 -3
- package/apps/studio/exporters/pptx.ts +6 -4
- package/apps/studio/exporters/svg.ts +9 -5
- package/apps/studio/exporters/video-encode-lib.ts +200 -0
- package/apps/studio/exporters/video-render-lib.ts +108 -0
- package/apps/studio/exporters/video.ts +184 -0
- package/apps/studio/history.ts +47 -1
- package/apps/studio/http.ts +758 -27
- package/apps/studio/input-router.tsx +19 -1
- package/apps/studio/marquee-overlay.tsx +1 -1
- package/apps/studio/measure-overlay.tsx +241 -0
- package/apps/studio/participants-chrome.tsx +3 -3
- package/apps/studio/runtime-bundle.ts +30 -0
- package/apps/studio/server.ts +34 -9
- package/apps/studio/sizing-mode.ts +117 -0
- package/apps/studio/spacing-handles.ts +166 -0
- package/apps/studio/test/annotate-write.test.ts +890 -0
- package/apps/studio/test/annotations-roundtrip.test.ts +47 -0
- package/apps/studio/test/camera-reveal.test.tsx +173 -0
- package/apps/studio/test/canvas-create-api.test.ts +76 -0
- package/apps/studio/test/canvas-edit.test.ts +140 -0
- package/apps/studio/test/canvas-list-watch.test.ts +49 -0
- package/apps/studio/test/canvas-media-drop.test.ts +30 -1
- package/apps/studio/test/canvas-origin-gate.test.ts +54 -0
- package/apps/studio/test/canvas-rects.test.ts +198 -0
- package/apps/studio/test/clip-addressing.test.ts +732 -0
- package/apps/studio/test/comments-overlay.test.ts +117 -0
- package/apps/studio/test/config-reload.test.ts +230 -0
- package/apps/studio/test/dns-rebinding-guard.test.ts +74 -0
- package/apps/studio/test/dom-selection.test.ts +130 -0
- package/apps/studio/test/edit-css-occurrence.test.ts +81 -0
- package/apps/studio/test/edit-persistence.test.ts +91 -0
- package/apps/studio/test/edit-scope-api.test.ts +115 -0
- package/apps/studio/test/element-resize.test.ts +136 -0
- package/apps/studio/test/element-structural-api.test.ts +360 -0
- package/apps/studio/test/element-structural-edit.test.ts +233 -0
- package/apps/studio/test/equal-spacing-detector.test.ts +165 -1
- package/apps/studio/test/exporters/runtime.test.ts +59 -0
- package/apps/studio/test/file-lock.test.ts +84 -0
- package/apps/studio/test/fixtures/video-comp-fixture.tsx +82 -0
- package/apps/studio/test/history-rollback.test.ts +26 -0
- package/apps/studio/test/knob-props-authored.test.ts +87 -0
- package/apps/studio/test/read-annotations.test.ts +154 -0
- package/apps/studio/test/sizing-mode.test.ts +102 -0
- package/apps/studio/test/spacing-handles.test.ts +138 -0
- package/apps/studio/test/specimen-select.test.ts +88 -0
- package/apps/studio/test/timeline-parse.test.ts +127 -0
- package/apps/studio/test/timeline-snap.test.ts +85 -0
- package/apps/studio/test/tool-palette-insert-anchor.test.ts +84 -0
- package/apps/studio/test/undo-sequence-byte-compare.test.ts +211 -0
- package/apps/studio/test/video-asset.test.ts +163 -0
- package/apps/studio/test/video-comp-fixture.test.ts +50 -0
- package/apps/studio/test/video-comp.test.ts +168 -0
- package/apps/studio/test/video-render-bridge.test.ts +149 -0
- package/apps/studio/tool-palette.tsx +122 -2
- package/apps/studio/undo-hud.tsx +2 -2
- package/apps/studio/use-annotation-resize.tsx +6 -3
- package/apps/studio/use-canvas-media-drop.tsx +66 -4
- package/apps/studio/use-element-resize.tsx +732 -0
- package/apps/studio/use-keyboard-discipline.tsx +205 -15
- package/apps/studio/use-selection-set.tsx +14 -0
- package/apps/studio/use-spacing-handles.tsx +388 -0
- package/apps/studio/video-comp.tsx +444 -0
- package/apps/studio/whats-new.json +55 -0
- package/apps/studio/ws.ts +5 -0
- package/cli/commands/design.mjs +6 -1
- package/cli/commands/design.test.mjs +49 -1
- package/cli/lib/fetch-asset.test.mjs +213 -0
- package/package.json +8 -8
- package/plugins/design/dependencies.json +10 -2
- package/plugins/design/templates/_shell.html +25 -2
- package/plugins/design/templates/design-system-inspiration/_MAPPING.md +1 -1
|
@@ -47,6 +47,16 @@
|
|
|
47
47
|
* useTheme() Current theme + setter, syncs to <html data-theme>.
|
|
48
48
|
* useArtboardBounds(ref) ResizeObserver wrapper returning {width,height}.
|
|
49
49
|
*
|
|
50
|
+
* Whiteboard toolkit (feature-whiteboard-ai-toolkit) ────────────────────
|
|
51
|
+
* window.__maudeCanvasRects() Installed as a side effect on module load.
|
|
52
|
+
* Returns { artboards, elements, elementsTruncated } in
|
|
53
|
+
* WORLD coords — the geometry manifest `maude design
|
|
54
|
+
* canvas-rects` reads via headless Chromium so the
|
|
55
|
+
* annotation read/write verbs never hand-compute a
|
|
56
|
+
* coordinate. See the section right after DCArtboard.
|
|
57
|
+
* getLiveViewport() The live pan/zoom, readable outside the React tree —
|
|
58
|
+
* what the hook above converts screen rects through.
|
|
59
|
+
*
|
|
50
60
|
* Authoring vocabulary. Lift these before re-implementing equivalents. The
|
|
51
61
|
* surface intentionally mirrors the .html-era specimen idioms one-for-one
|
|
52
62
|
* (`.specimen-hd`, `.specimen-meta`, `.sku`, `.swatch`, `.stamp`) so existing
|
|
@@ -95,6 +105,7 @@ import {
|
|
|
95
105
|
buildMoveArtboardsRecord,
|
|
96
106
|
diffLayoutPositions,
|
|
97
107
|
} from './commands/move-artboards-command.ts';
|
|
108
|
+
import { scopedCdSelector, selectorIndex, shortText } from './dom-selection.ts';
|
|
98
109
|
import { AgentPresenceProvider, useAgentPresence } from './use-agent-presence.tsx';
|
|
99
110
|
import { type DragState, useArtboardDrag } from './use-artboard-drag.tsx';
|
|
100
111
|
import {
|
|
@@ -108,6 +119,13 @@ import { useSelectionSetOptional } from './use-selection-set.tsx';
|
|
|
108
119
|
import { MaybeToolProvider, useToolModeOptional } from './use-tool-mode.tsx';
|
|
109
120
|
import { UndoStackProvider, useUndoSinks, useUndoStackOptional } from './use-undo-stack.tsx';
|
|
110
121
|
|
|
122
|
+
export type { CompSnapshot, VideoCompMeta, VideoCompProps } from './video-comp.tsx';
|
|
123
|
+
// DDR-148 — video-comp canvas kind. Re-exported so `@maude/canvas-lib` exposes
|
|
124
|
+
// <VideoComp> (Remotion composition mounted in <Player> + the deterministic
|
|
125
|
+
// seek bridge). Its `remotion`/`@remotion/player` imports resolve through the
|
|
126
|
+
// canvas importmap (RUNTIME_PACKAGES), same as react/motion.
|
|
127
|
+
export { VideoComp } from './video-comp.tsx';
|
|
128
|
+
|
|
111
129
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
112
130
|
// Module constants
|
|
113
131
|
|
|
@@ -239,6 +257,28 @@ const ENGINE_CSS = `
|
|
|
239
257
|
display: flex;
|
|
240
258
|
flex-direction: column;
|
|
241
259
|
overflow: hidden;
|
|
260
|
+
/* Per-board compositor freeze (RCA: large filter/blend-heavy canvases jank on
|
|
261
|
+
pan/zoom). Promote each artboard to its own GPU layer that rasterizes ONCE
|
|
262
|
+
into a fixed bitmap the compositor then translates/scales — instead of
|
|
263
|
+
re-running its paint every frame. Load-bearing on WebKit/WKWebView, which
|
|
264
|
+
does NOT hardware-accelerate SVG url() reference filters or feTurbulence
|
|
265
|
+
(only CSS shorthand filters), so a scale change would otherwise re-RASTER
|
|
266
|
+
every board's poster/grain/blend at once (the fit-all zoom cliff).
|
|
267
|
+
will-change:transform (NOT plain translateZ(0)) is the fix: it freezes the
|
|
268
|
+
rastered bitmap so it never re-rasters under transform updates — fit-all pan
|
|
269
|
+
composites finished textures, zoom SCALES them (sharp when zoomed out).
|
|
270
|
+
Filters stay LIVE in the DOM — nothing baked — so the exact look and full
|
|
271
|
+
editability are preserved; paint just happens once per board, not per frame.
|
|
272
|
+
isolation:isolate bounds each board's blend backdrop so the texture is
|
|
273
|
+
self-contained/cacheable (isolation/contain do not themselves promote —
|
|
274
|
+
will-change does). content-visibility (on .dc-positioned) culls off-screen
|
|
275
|
+
boards so the promoted-layer count + GPU memory stay bounded to what's in
|
|
276
|
+
view. NB: this is the realistic ceiling on WebKit — a Chromium engine (Blink)
|
|
277
|
+
GPU-accelerates url() filters and renders such canvases more smoothly.
|
|
278
|
+
See .ai/logs/rca/issue-canvas-pan-zoom-jank-large-moodboard.md. */
|
|
279
|
+
isolation: isolate;
|
|
280
|
+
contain: paint;
|
|
281
|
+
will-change: transform;
|
|
242
282
|
}
|
|
243
283
|
.dc-canvas .dc-artboard.dc-positioned { position: absolute; }
|
|
244
284
|
.dc-canvas .dc-artboard-label {
|
|
@@ -678,6 +718,15 @@ function fitRectIntoHost(rect: ArtboardRect, hostEl: HTMLElement, pad = 24): Vie
|
|
|
678
718
|
// then. Written on every applyViewport (exact — no settle/publish lag).
|
|
679
719
|
let liveViewport: ViewportState | null = null;
|
|
680
720
|
|
|
721
|
+
/**
|
|
722
|
+
* The current pan/zoom, read from OUTSIDE the React tree — the
|
|
723
|
+
* `window.__maudeCanvasRects()` hook (below) has no component instance to
|
|
724
|
+
* hook into, so it reads this module-scope mirror instead of WorldContext.
|
|
725
|
+
*/
|
|
726
|
+
export function getLiveViewport(): ViewportState | null {
|
|
727
|
+
return liveViewport ? { ...liveViewport } : null;
|
|
728
|
+
}
|
|
729
|
+
|
|
681
730
|
export function useViewportController(opts: ViewportControllerOptions): ViewportControllerHandle {
|
|
682
731
|
const {
|
|
683
732
|
hostRef,
|
|
@@ -697,6 +746,13 @@ export function useViewportController(opts: ViewportControllerOptions): Viewport
|
|
|
697
746
|
const [isInteracting, setIsInteracting] = useState(false);
|
|
698
747
|
const interactingRef = useRef(false);
|
|
699
748
|
const isInteractingStateRef = useRef(false);
|
|
749
|
+
// Snapshot of the zoom at the START of each interaction burst. Lets the
|
|
750
|
+
// WebKit settle re-raster (writeTransform crisp) fire ONLY when the gesture
|
|
751
|
+
// actually changed scale — a pure pan is scale-invariant, so re-rasterizing
|
|
752
|
+
// the whole world plane on pan-settle is wasted work (the "seká po dojezdu"
|
|
753
|
+
// tail-spike on large canvases). RCA:
|
|
754
|
+
// .ai/logs/rca/issue-canvas-pan-zoom-jank-large-moodboard.md
|
|
755
|
+
const zoomAtInteractStartRef = useRef(1);
|
|
700
756
|
|
|
701
757
|
// Throttle / settle timers.
|
|
702
758
|
const publishTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
@@ -781,6 +837,9 @@ export function useViewportController(opts: ViewportControllerOptions): Viewport
|
|
|
781
837
|
interactingRef.current = true;
|
|
782
838
|
if (!isInteractingStateRef.current) {
|
|
783
839
|
isInteractingStateRef.current = true;
|
|
840
|
+
// Start of a fresh gesture burst — remember the scale so settle can tell
|
|
841
|
+
// a pure pan (skip the crisp re-raster) from a zoom (needs it).
|
|
842
|
+
zoomAtInteractStartRef.current = vpRef.current.zoom;
|
|
784
843
|
setIsInteracting(true);
|
|
785
844
|
}
|
|
786
845
|
if (interactEndTimerRef.current != null) clearTimeout(interactEndTimerRef.current);
|
|
@@ -792,7 +851,12 @@ export function useViewportController(opts: ViewportControllerOptions): Viewport
|
|
|
792
851
|
// Motion has stopped — re-write the world transform in crisp mode so the
|
|
793
852
|
// WebKit path drops its compositor layer and re-rasterizes text sharply at
|
|
794
853
|
// the settled scale. No-op on the Blink path (CSS `zoom` is already crisp).
|
|
795
|
-
|
|
854
|
+
// Skip it when the burst never changed scale (pure pan): the layer is
|
|
855
|
+
// already crisp at this zoom, so releasing + repainting the whole world
|
|
856
|
+
// plane would be a wasted main-thread spike on large canvases.
|
|
857
|
+
if (vpRef.current.zoom !== zoomAtInteractStartRef.current) {
|
|
858
|
+
writeTransform(vpRef.current, { crisp: true });
|
|
859
|
+
}
|
|
796
860
|
}, 220);
|
|
797
861
|
}, [writeTransform]);
|
|
798
862
|
|
|
@@ -950,6 +1014,7 @@ export function useViewportController(opts: ViewportControllerOptions): Viewport
|
|
|
950
1014
|
const initial = getInitial();
|
|
951
1015
|
if (initial) {
|
|
952
1016
|
vpRef.current = { ...initial };
|
|
1017
|
+
liveViewport = { ...initial }; // mirror pre-first-gesture so headless capture sees it
|
|
953
1018
|
writeTransform(vpRef.current, { crisp: true });
|
|
954
1019
|
setViewportPublished({ ...vpRef.current });
|
|
955
1020
|
}
|
|
@@ -963,6 +1028,7 @@ export function useViewportController(opts: ViewportControllerOptions): Viewport
|
|
|
963
1028
|
hadSize = true;
|
|
964
1029
|
const refit = computeFitRef.current();
|
|
965
1030
|
vpRef.current = { ...refit };
|
|
1031
|
+
liveViewport = { ...refit };
|
|
966
1032
|
writeTransform(vpRef.current, { crisp: true });
|
|
967
1033
|
setViewportPublished({ ...vpRef.current });
|
|
968
1034
|
}
|
|
@@ -1497,6 +1563,18 @@ function DesignCanvasInner({ children, controls }: DesignCanvasProps) {
|
|
|
1497
1563
|
return bestId;
|
|
1498
1564
|
}, [artboards, controller.viewport]);
|
|
1499
1565
|
|
|
1566
|
+
// DDR-148 — report the viewport-active artboard (nearest the centre) to the
|
|
1567
|
+
// shell so the Timeline panel FOLLOWS whichever artboard the user pans to,
|
|
1568
|
+
// not just the one they cmd+click. Fires on every pan/zoom that changes it.
|
|
1569
|
+
useEffect(() => {
|
|
1570
|
+
if (typeof window === 'undefined' || window.parent === window) return;
|
|
1571
|
+
try {
|
|
1572
|
+
window.parent.postMessage({ dgn: 'active-artboard', id: activeArtboardId }, '*');
|
|
1573
|
+
} catch {
|
|
1574
|
+
/* cross-origin parent without our listener — no-op */
|
|
1575
|
+
}
|
|
1576
|
+
}, [activeArtboardId]);
|
|
1577
|
+
|
|
1500
1578
|
// The world's transform is owned by useViewportController (writes straight
|
|
1501
1579
|
// to `worldRef.current.style.transform`). Rendering the transform from
|
|
1502
1580
|
// React state instead would race: between React's commit and the
|
|
@@ -1772,7 +1850,23 @@ export function DCArtboard({
|
|
|
1772
1850
|
className={`dc-artboard dc-positioned${isInDrag ? ' dc-dragging' : ''}`}
|
|
1773
1851
|
data-dc-screen={id}
|
|
1774
1852
|
aria-current={isActive ? 'true' : undefined}
|
|
1775
|
-
style={{
|
|
1853
|
+
style={{
|
|
1854
|
+
left: liveX,
|
|
1855
|
+
top: liveY,
|
|
1856
|
+
width: rect.w,
|
|
1857
|
+
height: rect.h,
|
|
1858
|
+
// Off-screen artboards skip layout+paint. On a large multi-board
|
|
1859
|
+
// canvas (e.g. an 18-board moodboard) every board otherwise paints
|
|
1860
|
+
// onto one huge .dc-world plane whose device-pixel size exceeds
|
|
1861
|
+
// WebKit's ~16384px texture limit, forcing tile repaints on every
|
|
1862
|
+
// pan/zoom. content-visibility localizes each board's paint and culls
|
|
1863
|
+
// the ones outside the viewport; contain-intrinsic-size preserves the
|
|
1864
|
+
// box so the model-based fit math (computeFit reads ArtboardRects,
|
|
1865
|
+
// never the DOM) and drag hit-testing are unaffected. RCA:
|
|
1866
|
+
// .ai/logs/rca/issue-canvas-pan-zoom-jank-large-moodboard.md
|
|
1867
|
+
contentVisibility: 'auto',
|
|
1868
|
+
containIntrinsicSize: `${rect.w}px ${rect.h}px`,
|
|
1869
|
+
}}
|
|
1776
1870
|
{...handleProps}
|
|
1777
1871
|
>
|
|
1778
1872
|
<button type="button" className="dc-artboard-label sku" aria-label={`Artboard ${label}`}>
|
|
@@ -1793,6 +1887,148 @@ export function DCArtboard({
|
|
|
1793
1887
|
}
|
|
1794
1888
|
DCArtboard.displayName = 'DCArtboard';
|
|
1795
1889
|
|
|
1890
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1891
|
+
// window.__maudeCanvasRects() — the whiteboard-toolkit geometry manifest hook
|
|
1892
|
+
// (feature-whiteboard-ai-toolkit). `maude design canvas-rects` (a headless
|
|
1893
|
+
// Chromium shim) calls this to get every artboard's + every meaningful
|
|
1894
|
+
// element's WORLD-coordinate rect, so the annotation read/write verbs
|
|
1895
|
+
// (`read-annotations --rects`, `annotate --in/--pin/--board`) never need to
|
|
1896
|
+
// hand-compute a coordinate. Pure DOM + the `liveViewport` mirror above — no
|
|
1897
|
+
// React context needed, so it works from a plain page.evaluate() call with no
|
|
1898
|
+
// regard for which component tree happens to be mounted.
|
|
1899
|
+
//
|
|
1900
|
+
// World-coord conversion mirrors DesignCanvasInner's `activeArtboardId` math
|
|
1901
|
+
// (canvas = translate(vp.x,vp.y) then scale(vp.zoom) applied to `.dc-world`):
|
|
1902
|
+
// worldX = (screenX - hostRect.left - vp.x) / vp.zoom
|
|
1903
|
+
// A `.dc-canvas` host is required (bare DS specimens have no world plane —
|
|
1904
|
+
// this hook is for UI canvases with artboards, so it returns an empty
|
|
1905
|
+
// manifest there rather than guessing).
|
|
1906
|
+
|
|
1907
|
+
export interface CanvasRectsElement {
|
|
1908
|
+
cdId: string | null;
|
|
1909
|
+
selector: string;
|
|
1910
|
+
index: number;
|
|
1911
|
+
artboard: string | null;
|
|
1912
|
+
x: number;
|
|
1913
|
+
y: number;
|
|
1914
|
+
w: number;
|
|
1915
|
+
h: number;
|
|
1916
|
+
tag: string;
|
|
1917
|
+
text: string;
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
export interface CanvasRectsManifest {
|
|
1921
|
+
artboards: ArtboardRect[];
|
|
1922
|
+
elements: CanvasRectsElement[];
|
|
1923
|
+
elementsTruncated: boolean;
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
// Comment/annotation/chrome subtrees never make useful annotation targets —
|
|
1927
|
+
// mirrors the pickSpecimenEl chrome denylist (canvas-comment-mount.tsx) plus
|
|
1928
|
+
// the annotation layer's own root.
|
|
1929
|
+
const RECTS_CHROME_SELECTOR =
|
|
1930
|
+
'.cm-composer, .cm-thread, .cm-mention-popup, .cm-pin, [data-mc-hover-halo], [data-mdcc-annotations], .dc-minimap, .dc-zoom-toolbar';
|
|
1931
|
+
|
|
1932
|
+
const RECTS_INTERACTIVE_TAGS = new Set([
|
|
1933
|
+
'button',
|
|
1934
|
+
'a',
|
|
1935
|
+
'input',
|
|
1936
|
+
'textarea',
|
|
1937
|
+
'select',
|
|
1938
|
+
'option',
|
|
1939
|
+
'label',
|
|
1940
|
+
'summary',
|
|
1941
|
+
'img',
|
|
1942
|
+
'svg',
|
|
1943
|
+
'video',
|
|
1944
|
+
'picture',
|
|
1945
|
+
]);
|
|
1946
|
+
|
|
1947
|
+
const RECTS_ELEMENT_CAP = 400;
|
|
1948
|
+
|
|
1949
|
+
/** True when `el` has no `[data-cd-id]` descendant — a "leaf" in the pipeline's
|
|
1950
|
+
* stamped tree, i.e. the finest element the JSX source actually distinguishes
|
|
1951
|
+
* (a text run, an icon, a leaf `<div>`) rather than a structural wrapper. */
|
|
1952
|
+
function isCdIdLeaf(el: Element): boolean {
|
|
1953
|
+
return el.querySelector('[data-cd-id]') === null;
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
function elementIsCandidate(el: Element): boolean {
|
|
1957
|
+
const tag = el.tagName.toLowerCase();
|
|
1958
|
+
if (RECTS_INTERACTIVE_TAGS.has(tag)) return true;
|
|
1959
|
+
if (el.hasAttribute('role') && el.getAttribute('role') === 'button') return true;
|
|
1960
|
+
if (el.hasAttribute('tabindex')) return true;
|
|
1961
|
+
if (!isCdIdLeaf(el)) return false; // structural wrapper — a leaf sibling/descendant covers it
|
|
1962
|
+
return shortText(el, 1).length > 0;
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
function buildCanvasRectsManifest(): CanvasRectsManifest {
|
|
1966
|
+
const empty: CanvasRectsManifest = { artboards: [], elements: [], elementsTruncated: false };
|
|
1967
|
+
if (typeof document === 'undefined') return empty;
|
|
1968
|
+
const host = document.querySelector('.dc-canvas') as HTMLElement | null;
|
|
1969
|
+
if (!host) return empty; // bare specimen / no world plane — nothing to resolve against
|
|
1970
|
+
|
|
1971
|
+
const vp = getLiveViewport() ?? { x: 0, y: 0, zoom: 1 };
|
|
1972
|
+
const hostRect = host.getBoundingClientRect();
|
|
1973
|
+
const toWorld = (r: DOMRect): { x: number; y: number; w: number; h: number } => ({
|
|
1974
|
+
x: (r.left - hostRect.left - vp.x) / vp.zoom,
|
|
1975
|
+
y: (r.top - hostRect.top - vp.y) / vp.zoom,
|
|
1976
|
+
w: r.width / vp.zoom,
|
|
1977
|
+
h: r.height / vp.zoom,
|
|
1978
|
+
});
|
|
1979
|
+
|
|
1980
|
+
const artboards: ArtboardRect[] = [];
|
|
1981
|
+
const artboardEls = Array.from(document.querySelectorAll('[data-dc-screen]'));
|
|
1982
|
+
for (const el of artboardEls) {
|
|
1983
|
+
const rect = (el as HTMLElement).getBoundingClientRect();
|
|
1984
|
+
if (rect.width <= 0 || rect.height <= 0) continue;
|
|
1985
|
+
const id = el.getAttribute('data-dc-screen');
|
|
1986
|
+
if (!id) continue;
|
|
1987
|
+
artboards.push({ id, ...toWorld(rect) });
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
const elements: CanvasRectsElement[] = [];
|
|
1991
|
+
let truncated = false;
|
|
1992
|
+
const candidateEls = Array.from(document.querySelectorAll('[data-cd-id]'));
|
|
1993
|
+
for (const el of candidateEls) {
|
|
1994
|
+
if (elements.length >= RECTS_ELEMENT_CAP) {
|
|
1995
|
+
truncated = true;
|
|
1996
|
+
break;
|
|
1997
|
+
}
|
|
1998
|
+
if (el.closest(RECTS_CHROME_SELECTOR)) continue;
|
|
1999
|
+
const artboardEl = el.closest('[data-dc-screen]');
|
|
2000
|
+
if (!artboardEl) continue; // off-artboard chrome (menubar, panels, toolbars)
|
|
2001
|
+
if (!elementIsCandidate(el)) continue;
|
|
2002
|
+
const rect = (el as HTMLElement).getBoundingClientRect();
|
|
2003
|
+
if (rect.width <= 0 || rect.height <= 0) continue;
|
|
2004
|
+
const cdId = el.getAttribute('data-cd-id');
|
|
2005
|
+
const artboardId = artboardEl.getAttribute('data-dc-screen');
|
|
2006
|
+
const selector = cdId ? scopedCdSelector(cdId, artboardId) : '';
|
|
2007
|
+
const index = cdId ? selectorIndex(document, selector, el) : 0;
|
|
2008
|
+
elements.push({
|
|
2009
|
+
cdId,
|
|
2010
|
+
selector,
|
|
2011
|
+
index,
|
|
2012
|
+
artboard: artboardId,
|
|
2013
|
+
...toWorld(rect),
|
|
2014
|
+
tag: el.tagName.toLowerCase(),
|
|
2015
|
+
text: shortText(el, 120),
|
|
2016
|
+
});
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
return { artboards, elements, elementsTruncated: truncated };
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
declare global {
|
|
2023
|
+
interface Window {
|
|
2024
|
+
__maudeCanvasRects?: () => CanvasRectsManifest;
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
if (typeof window !== 'undefined') {
|
|
2029
|
+
window.__maudeCanvasRects = buildCanvasRectsManifest;
|
|
2030
|
+
}
|
|
2031
|
+
|
|
1796
2032
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
1797
2033
|
// DrawProof (Phase 25) — the render/verify harness for the draw engine. Renders
|
|
1798
2034
|
// ONE vector mark across a size ladder × {light, dark, single-color flatten} as
|
|
@@ -86,7 +86,11 @@ export function createCanvasListWatch(
|
|
|
86
86
|
// default config newCanvasDir IS a group; a config pointing it outside
|
|
87
87
|
// canvasGroups would have the canvas absent from /_index-data too, so the
|
|
88
88
|
// watcher stays consistent with what the tree can actually show.
|
|
89
|
-
|
|
89
|
+
//
|
|
90
|
+
// Read at USE time, never captured — `reloadConfig` (context.ts) hot-swaps
|
|
91
|
+
// ctx.cfg in place when config.json changes on disk, and a group added
|
|
92
|
+
// mid-session (`/design:setup-ds` adding `system`) must gate correctly.
|
|
93
|
+
const groupPaths = () => (ctx.cfg.canvasGroups ?? []).map((g) => g.path);
|
|
90
94
|
|
|
91
95
|
// null = not yet seeded; the first refresh captures the baseline and emits
|
|
92
96
|
// nothing (canvases already on disk at boot aren't "added").
|
|
@@ -156,7 +160,7 @@ export function createCanvasListWatch(
|
|
|
156
160
|
}
|
|
157
161
|
|
|
158
162
|
const off = ctx.bus.on('fs:any', (rel: string) => {
|
|
159
|
-
if (!isCanvasCandidate(rel, groupPaths)) return;
|
|
163
|
+
if (!isCanvasCandidate(rel, groupPaths())) return;
|
|
160
164
|
schedule();
|
|
161
165
|
});
|
|
162
166
|
|