@1agh/maude 0.41.0 → 0.43.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/apps/studio/.ai/cache/_stats.json +7 -0
- package/apps/studio/acp/bootstrap-brief.ts +12 -2
- package/apps/studio/annotations-layer.tsx +1055 -278
- package/apps/studio/annotations-model.ts +58 -0
- package/apps/studio/api.ts +173 -63
- package/apps/studio/bin/_html-playwright.mjs +26 -4
- package/apps/studio/bin/_ingest-footage.mjs +386 -0
- package/apps/studio/bin/_pdf-playwright.mjs +13 -2
- package/apps/studio/bin/_png-playwright.mjs +15 -2
- package/apps/studio/bin/_pptx-playwright.mjs +17 -4
- package/apps/studio/bin/_probe-footage-playwright.mjs +269 -0
- package/apps/studio/bin/_screenshot-playwright.mjs +17 -2
- package/apps/studio/bin/_svg-playwright.mjs +26 -4
- package/apps/studio/bin/_video-playwright.mjs +141 -71
- package/apps/studio/bin/ingest-footage.sh +27 -0
- package/apps/studio/bin/photo-adjust.sh +163 -0
- package/apps/studio/bin/photo-bg-remove.sh +160 -0
- package/apps/studio/bin/probe-footage.sh +28 -0
- package/apps/studio/bin/read-annotations.mjs +125 -2
- package/apps/studio/bin/screenshot.sh +53 -4
- package/apps/studio/canvas-edit.ts +459 -10
- package/apps/studio/canvas-lib.tsx +523 -5
- package/apps/studio/canvas-pipeline.ts +65 -7
- package/apps/studio/canvas-shell.tsx +156 -16
- package/apps/studio/client/app.jsx +476 -88
- package/apps/studio/client/export-center.jsx +460 -0
- package/apps/studio/client/github.js +4 -0
- package/apps/studio/client/panels/GitPanel.jsx +8 -0
- package/apps/studio/client/panels/RepoBranchSwitcher.jsx +133 -21
- package/apps/studio/client/panels/StickerPicker.jsx +160 -0
- package/apps/studio/client/panels/TimelinePanel.jsx +15 -2
- package/apps/studio/client/photo-knobs.jsx +437 -0
- package/apps/studio/client/styles/3-shell-maude.css +59 -0
- package/apps/studio/client/styles/4-components.css +150 -0
- package/apps/studio/commands/edit-source-command.ts +31 -4
- package/apps/studio/config.schema.json +2 -2
- package/apps/studio/context-menu.tsx +31 -6
- package/apps/studio/dist/client.bundle.js +5561 -19
- package/apps/studio/dist/comment-mount.js +2 -2
- package/apps/studio/dist/runtime/.min-sizes.json +1 -0
- package/apps/studio/dist/runtime/@imgly_background-removal.js +5543 -0
- package/apps/studio/dist/runtime/pixi-js.js +519 -519
- package/apps/studio/dist/styles.css +1 -1
- package/apps/studio/dom-selection.ts +25 -0
- package/apps/studio/export-dialog.tsx +18 -25
- package/apps/studio/exporters/_runtime.ts +104 -0
- package/apps/studio/exporters/html.ts +12 -20
- package/apps/studio/exporters/index.ts +14 -2
- package/apps/studio/exporters/jobs.ts +376 -0
- package/apps/studio/exporters/pdf.ts +16 -20
- package/apps/studio/exporters/png.ts +12 -20
- package/apps/studio/exporters/pptx.ts +22 -23
- package/apps/studio/exporters/scope.ts +1 -0
- package/apps/studio/exporters/svg.ts +14 -22
- package/apps/studio/exporters/video.ts +37 -19
- package/apps/studio/footage/schema.test.ts +195 -0
- package/apps/studio/footage/schema.ts +484 -0
- package/apps/studio/footage-store.ts +235 -0
- package/apps/studio/fs-watch.ts +10 -0
- package/apps/studio/git/endpoints.ts +68 -7
- package/apps/studio/git/service.ts +198 -20
- package/apps/studio/github/service.ts +67 -0
- package/apps/studio/handoff.ts +3 -2
- package/apps/studio/http.ts +318 -64
- package/apps/studio/input-router.tsx +10 -0
- package/apps/studio/inspect.ts +53 -1
- package/apps/studio/paths.ts +10 -0
- package/apps/studio/photo/filters.ts +285 -0
- package/apps/studio/photo/pipeline.ts +607 -0
- package/apps/studio/photo/schema.ts +367 -0
- package/apps/studio/photo-store.ts +154 -0
- package/apps/studio/runtime-bundle.ts +29 -6
- package/apps/studio/server.ts +8 -2
- package/apps/studio/stickers/figjam-doodle/manifest.json +104 -0
- package/apps/studio/stickers/figjam-doodle/slice1.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice10.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice11.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice12.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice13.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice14.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice15.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice16.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice17.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice18.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice19.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice2.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice20.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice21.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice22.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice23.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice24.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice3.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice4.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice5.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice6.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice7.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice8.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice9.png +0 -0
- package/apps/studio/stickers/life-style/best.png +0 -0
- package/apps/studio/stickers/life-style/come-on.png +0 -0
- package/apps/studio/stickers/life-style/cut.png +0 -0
- package/apps/studio/stickers/life-style/detox.png +0 -0
- package/apps/studio/stickers/life-style/just.png +0 -0
- package/apps/studio/stickers/life-style/manifest.json +68 -0
- package/apps/studio/stickers/life-style/mirror.png +0 -0
- package/apps/studio/stickers/life-style/nice.png +0 -0
- package/apps/studio/stickers/life-style/objects.png +0 -0
- package/apps/studio/stickers/life-style/ok.png +0 -0
- package/apps/studio/stickers/life-style/queen.png +0 -0
- package/apps/studio/stickers/life-style/star.png +0 -0
- package/apps/studio/stickers/life-style/sun.png +0 -0
- package/apps/studio/stickers/life-style/water.png +0 -0
- package/apps/studio/stickers/life-style/yeah.png +0 -0
- package/apps/studio/stickers/life-style/you-can.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/check-the-deets.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/cut-it.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/dope.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/fresh.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/hot.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/idea.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/keep-exploring.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/killed-it.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/love-it.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/manifest.json +88 -0
- package/apps/studio/stickers/opposing-thoughts/not-sure.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/ok.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/pure-gold.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/save-for-later.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/steal-this.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/take-a-peek.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/this-or-that.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/thoughts.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/vibes.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/winner.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/wip.png +0 -0
- package/apps/studio/stickers/project-status/group-100.png +0 -0
- package/apps/studio/stickers/project-status/group-101.png +0 -0
- package/apps/studio/stickers/project-status/group-102.png +0 -0
- package/apps/studio/stickers/project-status/group-103.png +0 -0
- package/apps/studio/stickers/project-status/group-104.png +0 -0
- package/apps/studio/stickers/project-status/group-105.png +0 -0
- package/apps/studio/stickers/project-status/group-106.png +0 -0
- package/apps/studio/stickers/project-status/group-107.png +0 -0
- package/apps/studio/stickers/project-status/group-108.png +0 -0
- package/apps/studio/stickers/project-status/group-109.png +0 -0
- package/apps/studio/stickers/project-status/group-110.png +0 -0
- package/apps/studio/stickers/project-status/group-111.png +0 -0
- package/apps/studio/stickers/project-status/group-112.png +0 -0
- package/apps/studio/stickers/project-status/group-113.png +0 -0
- package/apps/studio/stickers/project-status/group-114.png +0 -0
- package/apps/studio/stickers/project-status/group-115.png +0 -0
- package/apps/studio/stickers/project-status/group-117.png +0 -0
- package/apps/studio/stickers/project-status/group-118.png +0 -0
- package/apps/studio/stickers/project-status/group-119.png +0 -0
- package/apps/studio/stickers/project-status/group-120.png +0 -0
- package/apps/studio/stickers/project-status/group-121.png +0 -0
- package/apps/studio/stickers/project-status/group-122.png +0 -0
- package/apps/studio/stickers/project-status/group-41.png +0 -0
- package/apps/studio/stickers/project-status/group-42.png +0 -0
- package/apps/studio/stickers/project-status/group-43.png +0 -0
- package/apps/studio/stickers/project-status/group-44.png +0 -0
- package/apps/studio/stickers/project-status/group-45.png +0 -0
- package/apps/studio/stickers/project-status/group-46.png +0 -0
- package/apps/studio/stickers/project-status/group-47.png +0 -0
- package/apps/studio/stickers/project-status/group-48.png +0 -0
- package/apps/studio/stickers/project-status/group-49.png +0 -0
- package/apps/studio/stickers/project-status/group-50.png +0 -0
- package/apps/studio/stickers/project-status/group-51.png +0 -0
- package/apps/studio/stickers/project-status/group-52.png +0 -0
- package/apps/studio/stickers/project-status/group-53.png +0 -0
- package/apps/studio/stickers/project-status/group-54.png +0 -0
- package/apps/studio/stickers/project-status/group-55.png +0 -0
- package/apps/studio/stickers/project-status/group-56.png +0 -0
- package/apps/studio/stickers/project-status/group-57.png +0 -0
- package/apps/studio/stickers/project-status/group-58.png +0 -0
- package/apps/studio/stickers/project-status/group-59.png +0 -0
- package/apps/studio/stickers/project-status/group-60.png +0 -0
- package/apps/studio/stickers/project-status/group-61.png +0 -0
- package/apps/studio/stickers/project-status/group-62.png +0 -0
- package/apps/studio/stickers/project-status/group-63.png +0 -0
- package/apps/studio/stickers/project-status/group-64.png +0 -0
- package/apps/studio/stickers/project-status/group-65.png +0 -0
- package/apps/studio/stickers/project-status/group-66.png +0 -0
- package/apps/studio/stickers/project-status/group-67.png +0 -0
- package/apps/studio/stickers/project-status/group-68.png +0 -0
- package/apps/studio/stickers/project-status/group-69.png +0 -0
- package/apps/studio/stickers/project-status/group-70.png +0 -0
- package/apps/studio/stickers/project-status/group-71.png +0 -0
- package/apps/studio/stickers/project-status/group-72.png +0 -0
- package/apps/studio/stickers/project-status/group-73.png +0 -0
- package/apps/studio/stickers/project-status/group-74.png +0 -0
- package/apps/studio/stickers/project-status/group-75.png +0 -0
- package/apps/studio/stickers/project-status/group-76.png +0 -0
- package/apps/studio/stickers/project-status/group-77.png +0 -0
- package/apps/studio/stickers/project-status/group-78.png +0 -0
- package/apps/studio/stickers/project-status/group-79.png +0 -0
- package/apps/studio/stickers/project-status/group-80.png +0 -0
- package/apps/studio/stickers/project-status/group-81.png +0 -0
- package/apps/studio/stickers/project-status/group-82.png +0 -0
- package/apps/studio/stickers/project-status/group-83.png +0 -0
- package/apps/studio/stickers/project-status/group-84.png +0 -0
- package/apps/studio/stickers/project-status/group-85.png +0 -0
- package/apps/studio/stickers/project-status/group-86.png +0 -0
- package/apps/studio/stickers/project-status/group-87.png +0 -0
- package/apps/studio/stickers/project-status/group-88.png +0 -0
- package/apps/studio/stickers/project-status/group-89.png +0 -0
- package/apps/studio/stickers/project-status/group-90.png +0 -0
- package/apps/studio/stickers/project-status/group-91.png +0 -0
- package/apps/studio/stickers/project-status/group-92.png +0 -0
- package/apps/studio/stickers/project-status/group-93.png +0 -0
- package/apps/studio/stickers/project-status/group-94.png +0 -0
- package/apps/studio/stickers/project-status/group-96.png +0 -0
- package/apps/studio/stickers/project-status/group-97.png +0 -0
- package/apps/studio/stickers/project-status/group-98.png +0 -0
- package/apps/studio/stickers/project-status/group-99.png +0 -0
- package/apps/studio/stickers/project-status/manifest.json +328 -0
- package/apps/studio/test/acp-bootstrap-brief.test.ts +11 -0
- package/apps/studio/test/acp-commands.test.ts +2 -1
- package/apps/studio/test/asset-api.test.ts +3 -3
- package/apps/studio/test/canvas-edit.test.ts +17 -6
- package/apps/studio/test/canvas-origin-gate.test.ts +19 -0
- package/apps/studio/test/dns-rebinding-guard.test.ts +27 -0
- package/apps/studio/test/dynamic-text-edit.test.ts +162 -0
- package/apps/studio/test/edit-source-command.test.ts +23 -0
- package/apps/studio/test/element-structural-edit.test.ts +46 -0
- package/apps/studio/test/export-center.test.tsx +287 -0
- package/apps/studio/test/export-shim-multi-capture.test.ts +83 -0
- package/apps/studio/test/exporters/history.test.ts +32 -3
- package/apps/studio/test/exporters/jobs.test.ts +263 -0
- package/apps/studio/test/footage-store.test.ts +100 -0
- package/apps/studio/test/git-branches.test.ts +97 -0
- package/apps/studio/test/github-api.test.ts +56 -1
- package/apps/studio/test/input-router.test.ts +33 -0
- package/apps/studio/test/inspect-script-syntax.test.ts +53 -0
- package/apps/studio/test/photo-canvas-bundle.test.ts +61 -0
- package/apps/studio/test/photo-edit-api.test.ts +201 -0
- package/apps/studio/test/photo-filters.test.ts +149 -0
- package/apps/studio/test/photo-pipeline.test.ts +106 -0
- package/apps/studio/test/photo-taxonomy.test.ts +96 -0
- package/apps/studio/test/read-annotations.test.ts +164 -0
- package/apps/studio/test/shell-importmap.test.ts +49 -0
- package/apps/studio/test/text-editability-stamp.test.ts +131 -0
- package/apps/studio/test/timeline-parse.test.ts +57 -0
- package/apps/studio/test/tool-palette-insert-anchor.test.ts +5 -3
- package/apps/studio/test/undo-stack.test.ts +33 -0
- package/apps/studio/test/video-asset.test.ts +5 -5
- package/apps/studio/test/video-render-bridge.test.ts +23 -1
- package/apps/studio/text-caret.ts +239 -0
- package/apps/studio/tool-palette.tsx +43 -20
- package/apps/studio/undo-stack.ts +94 -2
- package/apps/studio/use-annotation-resize.tsx +4 -4
- package/apps/studio/use-canvas-media-drop.tsx +38 -1
- package/apps/studio/use-selection-set.tsx +21 -0
- package/apps/studio/video-comp.tsx +48 -7
- package/apps/studio/whats-new.json +70 -0
- package/apps/studio/ws.ts +6 -0
- package/cli/commands/design.mjs +34 -3
- package/cli/lib/gitignore-block.mjs +2 -0
- package/package.json +10 -10
- package/plugins/design/templates/_shell.html +1 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# photo-adjust.sh — feature-photo-editor (Stage G, Task 19). Headless PARAMETRIC
|
|
3
|
+
# photo edit: assemble a PhotoEdit from flags and PUT it to the running dev
|
|
4
|
+
# server's /_api/photo-edit route so an agent (or a human) can tune a photo
|
|
5
|
+
# without opening a browser or clicking a slider.
|
|
6
|
+
#
|
|
7
|
+
# Deliberately the THIN, NON-BROWSER sibling of photo-bg-remove.sh. Background
|
|
8
|
+
# removal needs a browser round-trip because @imgly runs the ML model client-
|
|
9
|
+
# side; parametric edits are pure JSON, so a direct curl to the already-validated
|
|
10
|
+
# route (the photo-store cap stack — validatePhotoEdit + sha8 + containment +
|
|
11
|
+
# size cap — is the gate) is the whole job. DO NOT "fix" this asymmetry by making
|
|
12
|
+
# this verb heavier: there is no inference step here, so a harness canvas +
|
|
13
|
+
# agent-browser would be pure overhead.
|
|
14
|
+
#
|
|
15
|
+
# Reached via `maude design photo-adjust` (never a raw bin path — DDR-062).
|
|
16
|
+
#
|
|
17
|
+
# Usage:
|
|
18
|
+
# photo-adjust.sh --asset <assets/<sha8>.<ext> | <sha8>> [--root <repo>]
|
|
19
|
+
# [--brightness N] [--contrast N] [--saturation N] [--exposure N]
|
|
20
|
+
# [--hue N] [--sepia N] [--grayscale N] [--invert N]
|
|
21
|
+
# [--duotone "#aabbcc,#ddeeff[,intensity]"]
|
|
22
|
+
# [--grain "amount[,size]"]
|
|
23
|
+
# [--pattern "type[,scale,opacity,blend]"]
|
|
24
|
+
# [--mask "preset[,strength]"]
|
|
25
|
+
# [--replace] (overwrite the sidecar instead of merging onto it)
|
|
26
|
+
# [--reset] (clear the sidecar to neutral / unedited)
|
|
27
|
+
#
|
|
28
|
+
# Adjustment ranges (normalized, 0 = neutral): brightness/contrast/saturation/
|
|
29
|
+
# exposure ∈ −1..1, hue ∈ −180..180 (deg), sepia/grayscale/invert ∈ 0..1.
|
|
30
|
+
# Merges onto the existing sidecar by default (successive calls accumulate).
|
|
31
|
+
#
|
|
32
|
+
# Requires a running dev server (caller runs `maude design server-up` first);
|
|
33
|
+
# reads the port from <designRoot>/_server.json.
|
|
34
|
+
#
|
|
35
|
+
# Stdout (last line): the sidecar path `assets/<sha8>.photo.json` (for $(...)).
|
|
36
|
+
# Stderr: progress / diagnostics.
|
|
37
|
+
# Exit: 0 ok / 1 server or asset problem / 2 bad args / 3 server rejected edit.
|
|
38
|
+
|
|
39
|
+
set -euo pipefail
|
|
40
|
+
|
|
41
|
+
ASSET="" REPO="" REPLACE=0 RESET=0
|
|
42
|
+
BRIGHTNESS="" CONTRAST="" SATURATION="" EXPOSURE="" HUE="" SEPIA="" GRAYSCALE="" INVERT=""
|
|
43
|
+
DUOTONE="" GRAIN="" PATTERN="" MASK=""
|
|
44
|
+
|
|
45
|
+
while [ $# -gt 0 ]; do
|
|
46
|
+
case "$1" in
|
|
47
|
+
--asset) ASSET="$2"; shift 2 ;;
|
|
48
|
+
--root) REPO="$2"; shift 2 ;;
|
|
49
|
+
--brightness) BRIGHTNESS="$2"; shift 2 ;;
|
|
50
|
+
--contrast) CONTRAST="$2"; shift 2 ;;
|
|
51
|
+
--saturation) SATURATION="$2"; shift 2 ;;
|
|
52
|
+
--exposure) EXPOSURE="$2"; shift 2 ;;
|
|
53
|
+
--hue) HUE="$2"; shift 2 ;;
|
|
54
|
+
--sepia) SEPIA="$2"; shift 2 ;;
|
|
55
|
+
--grayscale) GRAYSCALE="$2"; shift 2 ;;
|
|
56
|
+
--invert) INVERT="$2"; shift 2 ;;
|
|
57
|
+
--duotone) DUOTONE="$2"; shift 2 ;;
|
|
58
|
+
--grain) GRAIN="$2"; shift 2 ;;
|
|
59
|
+
--pattern) PATTERN="$2"; shift 2 ;;
|
|
60
|
+
--mask) MASK="$2"; shift 2 ;;
|
|
61
|
+
--replace) REPLACE=1; shift ;;
|
|
62
|
+
--reset) RESET=1; shift ;;
|
|
63
|
+
--help|-h) sed -n '2,39p' "$0" | sed 's/^# \?//'; exit 0 ;;
|
|
64
|
+
*) echo "photo-adjust.sh: unknown arg '$1'" >&2; exit 2 ;;
|
|
65
|
+
esac
|
|
66
|
+
done
|
|
67
|
+
|
|
68
|
+
[ -n "$ASSET" ] || { echo "photo-adjust.sh: --asset is required" >&2; exit 2; }
|
|
69
|
+
command -v jq >/dev/null 2>&1 || { echo "photo-adjust.sh: jq is required" >&2; exit 1; }
|
|
70
|
+
|
|
71
|
+
if [ -z "$REPO" ]; then
|
|
72
|
+
REPO="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
|
73
|
+
fi
|
|
74
|
+
DESIGN_ROOT="$REPO/.design"
|
|
75
|
+
[ -d "$DESIGN_ROOT" ] || { echo "photo-adjust.sh: no .design/ under $REPO" >&2; exit 1; }
|
|
76
|
+
|
|
77
|
+
STATE="$DESIGN_ROOT/_server.json"
|
|
78
|
+
[ -f "$STATE" ] || { echo "photo-adjust.sh: no _server.json (start the dev server first)" >&2; exit 1; }
|
|
79
|
+
PORT=$(jq -r '.port // empty' "$STATE" 2>/dev/null)
|
|
80
|
+
[ -n "$PORT" ] || { echo "photo-adjust.sh: could not read port from $STATE" >&2; exit 1; }
|
|
81
|
+
|
|
82
|
+
BASE_URL="http://127.0.0.1:$PORT/_api/photo-edit"
|
|
83
|
+
# URL-encode the asset param minimally (slashes are safe as a query value here).
|
|
84
|
+
ENC_ASSET=$(printf '%s' "$ASSET" | jq -sRr @uri)
|
|
85
|
+
URL="$BASE_URL?asset=$ENC_ASSET"
|
|
86
|
+
|
|
87
|
+
# --reset short-circuits: write a neutral sidecar.
|
|
88
|
+
if [ "$RESET" -eq 1 ]; then
|
|
89
|
+
RESP=$(curl -s -X PUT -H 'Content-Type: application/json' -d '{}' "$URL")
|
|
90
|
+
OK=$(printf '%s' "$RESP" | jq -r '.ok // false' 2>/dev/null || echo false)
|
|
91
|
+
[ "$OK" = "true" ] || { echo "photo-adjust.sh: reset rejected: $RESP" >&2; exit 3; }
|
|
92
|
+
printf '%s\n' "$(printf '%s' "$RESP" | jq -r '.path')"
|
|
93
|
+
exit 0
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# Assemble the patch from set flags (unset flags are omitted entirely).
|
|
97
|
+
PATCH=$(jq -n \
|
|
98
|
+
--arg brightness "$BRIGHTNESS" --arg contrast "$CONTRAST" --arg saturation "$SATURATION" \
|
|
99
|
+
--arg exposure "$EXPOSURE" --arg hue "$HUE" --arg sepia "$SEPIA" \
|
|
100
|
+
--arg grayscale "$GRAYSCALE" --arg invert "$INVERT" \
|
|
101
|
+
--arg duotone "$DUOTONE" --arg grain "$GRAIN" --arg pattern "$PATTERN" --arg mask "$MASK" '
|
|
102
|
+
def n($s): if $s == "" then null else ($s | tonumber) end;
|
|
103
|
+
def adj:
|
|
104
|
+
{ brightness: n($brightness), contrast: n($contrast), saturation: n($saturation),
|
|
105
|
+
exposure: n($exposure), hue: n($hue), sepia: n($sepia),
|
|
106
|
+
grayscale: n($grayscale), invert: n($invert) }
|
|
107
|
+
| with_entries(select(.value != null));
|
|
108
|
+
def duo:
|
|
109
|
+
if $duotone == "" then null
|
|
110
|
+
else ($duotone | split(",")) as $p
|
|
111
|
+
| { enabled: true, colorA: $p[0], colorB: $p[1] }
|
|
112
|
+
+ (if ($p | length) > 2 then { intensity: ($p[2] | tonumber) } else {} end)
|
|
113
|
+
end;
|
|
114
|
+
def gr:
|
|
115
|
+
if $grain == "" then null
|
|
116
|
+
else ($grain | split(",")) as $p
|
|
117
|
+
| { enabled: true, amount: ($p[0] | tonumber) }
|
|
118
|
+
+ (if ($p | length) > 1 then { size: ($p[1] | tonumber) } else {} end)
|
|
119
|
+
end;
|
|
120
|
+
def pat:
|
|
121
|
+
if $pattern == "" then null
|
|
122
|
+
else ($pattern | split(",")) as $p
|
|
123
|
+
| { enabled: true, type: $p[0] }
|
|
124
|
+
+ (if ($p | length) > 1 then { scale: ($p[1] | tonumber) } else {} end)
|
|
125
|
+
+ (if ($p | length) > 2 then { opacity: ($p[2] | tonumber) } else {} end)
|
|
126
|
+
+ (if ($p | length) > 3 then { blend: $p[3] } else {} end)
|
|
127
|
+
end;
|
|
128
|
+
def msk:
|
|
129
|
+
if $mask == "" then null
|
|
130
|
+
else ($mask | split(",")) as $p
|
|
131
|
+
| { preset: $p[0] }
|
|
132
|
+
+ (if ($p | length) > 1 then { strength: ($p[1] | tonumber) } else {} end)
|
|
133
|
+
end;
|
|
134
|
+
({}
|
|
135
|
+
+ (adj as $a | if ($a | length) > 0 then { adjustments: $a } else {} end)
|
|
136
|
+
+ (duo as $d | if $d then { duotone: $d } else {} end)
|
|
137
|
+
+ (gr as $g | if $g then { grain: $g } else {} end)
|
|
138
|
+
+ (pat as $p | if $p then { pattern: $p } else {} end)
|
|
139
|
+
+ (msk as $m | if $m then { mask: $m } else {} end))
|
|
140
|
+
')
|
|
141
|
+
|
|
142
|
+
if [ "$(printf '%s' "$PATCH" | jq -r 'keys | length')" = "0" ]; then
|
|
143
|
+
echo "photo-adjust.sh: no edit flags given (nothing to do)" >&2
|
|
144
|
+
exit 2
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
if [ "$REPLACE" -eq 1 ]; then
|
|
148
|
+
BODY="$PATCH"
|
|
149
|
+
else
|
|
150
|
+
# Merge onto the current sidecar (deep object merge via jq's `*`), dropping the
|
|
151
|
+
# server-stamped `version` (it's re-stamped on write).
|
|
152
|
+
BASE=$(curl -s "$URL" 2>/dev/null || echo '{}')
|
|
153
|
+
printf '%s' "$BASE" | jq -e . >/dev/null 2>&1 || BASE='{}'
|
|
154
|
+
BODY=$(jq -n --argjson base "$BASE" --argjson patch "$PATCH" '($base | del(.version)) * $patch')
|
|
155
|
+
fi
|
|
156
|
+
|
|
157
|
+
RESP=$(curl -s -X PUT -H 'Content-Type: application/json' -d "$BODY" "$URL")
|
|
158
|
+
OK=$(printf '%s' "$RESP" | jq -r '.ok // false' 2>/dev/null || echo false)
|
|
159
|
+
if [ "$OK" != "true" ]; then
|
|
160
|
+
echo "photo-adjust.sh: server rejected the edit: $RESP" >&2
|
|
161
|
+
exit 3
|
|
162
|
+
fi
|
|
163
|
+
printf '%s\n' "$(printf '%s' "$RESP" | jq -r '.path')"
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# photo-bg-remove.sh — feature-photo-editor (Stage G, Task 18). Headless
|
|
3
|
+
# background removal: mirrors draw-proof.sh's structure (throwaway harness
|
|
4
|
+
# canvas + agent-browser drive) but the "proof" here isn't a screenshot ladder
|
|
5
|
+
# — it's the client-side @imgly ML pass actually running and reporting back
|
|
6
|
+
# through DOM attributes agent-browser polls, since there's no other channel
|
|
7
|
+
# to cross the process boundary out of a headless browser.
|
|
8
|
+
#
|
|
9
|
+
# Design note (the decision this task was originally deferred on): the harness
|
|
10
|
+
# canvas runs inside the split-origin (DDR-054) canvas iframe, but BOTH
|
|
11
|
+
# `/_api/asset` and `/_api/photo-edit` are already canvas-safe routes (see
|
|
12
|
+
# their CANVAS_SAFE_API comments in http.ts) — so the harness component
|
|
13
|
+
# (`PhotoBgRemoveHarness`, canvas-lib.tsx) posts the matte and the updated
|
|
14
|
+
# PhotoEdit sidecar directly from the canvas origin. No cross-origin relay,
|
|
15
|
+
# no base64-through-a-DOM-attribute round-trip needed.
|
|
16
|
+
#
|
|
17
|
+
# Reached via `maude design photo-bg-remove` (never a raw bin path — DDR-062).
|
|
18
|
+
#
|
|
19
|
+
# Usage:
|
|
20
|
+
# photo-bg-remove.sh --asset <assets/<sha8>.<ext>> [--root <repo>]
|
|
21
|
+
# [--slug <name>] [--timeout <secs>]
|
|
22
|
+
#
|
|
23
|
+
# Requires a running dev server (caller runs `maude design server-up` first);
|
|
24
|
+
# reads the port from <designRoot>/_server.json. Requires agent-browser (the
|
|
25
|
+
# ML pass needs a real WASM/WebGPU-capable browser driving real DOM state —
|
|
26
|
+
# there is no non-browser path, unlike photo-adjust.sh's plain curl).
|
|
27
|
+
#
|
|
28
|
+
# Stdout (last line): the new matte asset path `assets/<sha8>.png` (for $(...)).
|
|
29
|
+
# Stderr: progress / diagnostics.
|
|
30
|
+
# Exit: 0 success / 1 missing input, server, or agent-browser / 2 bad args
|
|
31
|
+
# / 3 drive/navigation failed / 4 the harness itself reported an error
|
|
32
|
+
# / 5 timed out waiting for the ML pass to finish.
|
|
33
|
+
|
|
34
|
+
set -uo pipefail
|
|
35
|
+
|
|
36
|
+
ASSET="" SLUG="" REPO="" TIMEOUT=150
|
|
37
|
+
|
|
38
|
+
while [ $# -gt 0 ]; do
|
|
39
|
+
case "$1" in
|
|
40
|
+
--asset) ASSET="$2"; shift 2 ;;
|
|
41
|
+
--slug) SLUG="$2"; shift 2 ;;
|
|
42
|
+
--root) REPO="$2"; shift 2 ;;
|
|
43
|
+
--timeout) TIMEOUT="$2"; shift 2 ;;
|
|
44
|
+
--help|-h)
|
|
45
|
+
sed -n '2,29p' "$0" | sed 's/^# \?//'
|
|
46
|
+
exit 0
|
|
47
|
+
;;
|
|
48
|
+
*)
|
|
49
|
+
echo "photo-bg-remove.sh: unknown arg '$1' (try --help)" >&2
|
|
50
|
+
exit 2
|
|
51
|
+
;;
|
|
52
|
+
esac
|
|
53
|
+
done
|
|
54
|
+
|
|
55
|
+
[ -n "$ASSET" ] || { echo "photo-bg-remove.sh: --asset <assets/<sha8>.<ext>> required" >&2; exit 2; }
|
|
56
|
+
# Strip a leading designRoot-relative slash/prefix if the caller passed one.
|
|
57
|
+
ASSET="${ASSET#/}"
|
|
58
|
+
case "$ASSET" in
|
|
59
|
+
assets/*) : ;;
|
|
60
|
+
*) echo "photo-bg-remove.sh: --asset must look like assets/<sha8>.<ext>, got '$ASSET'" >&2; exit 2 ;;
|
|
61
|
+
esac
|
|
62
|
+
|
|
63
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
64
|
+
[ -n "$SLUG" ] || SLUG=$(basename "$ASSET" | sed -E 's/\.[^.]+$//')
|
|
65
|
+
SLUG=$(bash "$SCRIPT_DIR/slug.sh" "$SLUG" 2>/dev/null || printf '%s' "$SLUG" | tr '[:upper:]' '[:lower:]')
|
|
66
|
+
|
|
67
|
+
# ---------- resolve repo + design root + port ----------
|
|
68
|
+
if [ -z "$REPO" ]; then
|
|
69
|
+
REPO="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
|
70
|
+
fi
|
|
71
|
+
DESIGN_ROOT="$REPO/.design"
|
|
72
|
+
[ -d "$DESIGN_ROOT" ] || { echo "photo-bg-remove.sh: no .design/ under $REPO" >&2; exit 1; }
|
|
73
|
+
[ -f "$DESIGN_ROOT/$ASSET" ] || { echo "photo-bg-remove.sh: asset not found: $DESIGN_ROOT/$ASSET" >&2; exit 1; }
|
|
74
|
+
|
|
75
|
+
STATE="$DESIGN_ROOT/_server.json"
|
|
76
|
+
[ -f "$STATE" ] || { echo "photo-bg-remove.sh: $STATE missing — run \`maude design server-up\` first" >&2; exit 1; }
|
|
77
|
+
if command -v jq >/dev/null 2>&1; then
|
|
78
|
+
PORT=$(jq -r .port "$STATE" 2>/dev/null)
|
|
79
|
+
else
|
|
80
|
+
PORT=$(sed -nE 's/.*"port"[[:space:]]*:[[:space:]]*([0-9]+).*/\1/p' "$STATE" | head -n1)
|
|
81
|
+
fi
|
|
82
|
+
[ -n "$PORT" ] || { echo "photo-bg-remove.sh: no port in $STATE" >&2; exit 1; }
|
|
83
|
+
|
|
84
|
+
# ---------- resolve agent-browser (hard dependency — see header) ----------
|
|
85
|
+
# Mirrors screenshot.sh's MAUDE_AGENT_BROWSER override (DDR-144 attacker-F4 — an
|
|
86
|
+
# explicit single-binary pointer, never a PATH prepend a same-user attacker
|
|
87
|
+
# could shadow) and its ensure-browser.sh chrome resolution.
|
|
88
|
+
AB="${MAUDE_AGENT_BROWSER:-agent-browser}"
|
|
89
|
+
command -v "$AB" >/dev/null 2>&1 || {
|
|
90
|
+
echo "photo-bg-remove.sh: agent-browser not found (required — the ML pass needs a real WASM/WebGPU browser, there is no non-browser fallback here)" >&2
|
|
91
|
+
exit 1
|
|
92
|
+
}
|
|
93
|
+
if [ -z "${AGENT_BROWSER_EXECUTABLE_PATH:-}" ]; then
|
|
94
|
+
ES_FLAGS="--quiet"
|
|
95
|
+
[ -z "${MAUDE_DEV_SERVER_ROOT:-}" ] && ES_FLAGS="--quiet --no-download"
|
|
96
|
+
BROWSER_PATH="$(bash "$SCRIPT_DIR/ensure-browser.sh" $ES_FLAGS 2>/dev/null)"
|
|
97
|
+
[ -n "$BROWSER_PATH" ] && export AGENT_BROWSER_EXECUTABLE_PATH="$BROWSER_PATH"
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
# ---------- generate the harness canvas ----------
|
|
101
|
+
PHOTO_DIR="$DESIGN_ROOT/_photo"
|
|
102
|
+
mkdir -p "$PHOTO_DIR"
|
|
103
|
+
HARNESS_TSX="$PHOTO_DIR/$SLUG.bgremove.tsx"
|
|
104
|
+
{
|
|
105
|
+
echo "import { PhotoBgRemoveHarness } from '@maude/canvas-lib';"
|
|
106
|
+
echo
|
|
107
|
+
echo "// AUTO-GENERATED by \`maude design photo-bg-remove\` — headless bg-removal proof"
|
|
108
|
+
echo "// harness. Lives under _photo/ (gitignored); safe to delete + regenerate."
|
|
109
|
+
echo "export default function PhotoBgRemoveCanvas() {"
|
|
110
|
+
printf ' return <PhotoBgRemoveHarness source=%s />;\n' "\"$ASSET\""
|
|
111
|
+
echo "}"
|
|
112
|
+
} > "$HARNESS_TSX"
|
|
113
|
+
echo "→ harness canvas: ${HARNESS_TSX#$REPO/}" >&2
|
|
114
|
+
|
|
115
|
+
REL="_photo/$SLUG.bgremove.tsx"
|
|
116
|
+
URL="http://localhost:$PORT/_canvas-shell.html?canvas=$REL"
|
|
117
|
+
SESSION="photo-bgremove-$SLUG-$$"
|
|
118
|
+
SEL="[data-photo-bgremove-status]"
|
|
119
|
+
|
|
120
|
+
cleanup() { "$AB" --session "$SESSION" close >/dev/null 2>&1 || true; }
|
|
121
|
+
trap cleanup EXIT
|
|
122
|
+
|
|
123
|
+
echo "→ driving $URL (session $SESSION)" >&2
|
|
124
|
+
if ! "$AB" --session "$SESSION" open "$URL" >&2; then
|
|
125
|
+
echo "photo-bg-remove.sh: agent-browser failed to open the harness canvas" >&2
|
|
126
|
+
exit 3
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
# ---------- poll the harness's status attribute ----------
|
|
130
|
+
# The ML pass includes a first-use ~40 MB model-weight fetch (Task 11/12) —
|
|
131
|
+
# this can dominate the wait far more than inference itself, hence the
|
|
132
|
+
# generous default timeout.
|
|
133
|
+
ELAPSED=0
|
|
134
|
+
STATUS="pending"
|
|
135
|
+
while [ "$ELAPSED" -lt "$TIMEOUT" ]; do
|
|
136
|
+
STATUS="$("$AB" --session "$SESSION" get attr "$SEL" data-photo-bgremove-status 2>/dev/null | tr -d '"')"
|
|
137
|
+
case "$STATUS" in
|
|
138
|
+
done|error) break ;;
|
|
139
|
+
esac
|
|
140
|
+
sleep 2
|
|
141
|
+
ELAPSED=$((ELAPSED + 2))
|
|
142
|
+
done
|
|
143
|
+
|
|
144
|
+
if [ "$STATUS" = "error" ]; then
|
|
145
|
+
ERR="$("$AB" --session "$SESSION" get attr "$SEL" data-photo-bgremove-error 2>/dev/null | tr -d '"')"
|
|
146
|
+
echo "photo-bg-remove.sh: harness reported an error: ${ERR:-unknown}" >&2
|
|
147
|
+
exit 4
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
if [ "$STATUS" != "done" ]; then
|
|
151
|
+
echo "photo-bg-remove.sh: timed out after ${TIMEOUT}s waiting for the ML pass (still '$STATUS')" >&2
|
|
152
|
+
exit 5
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
RESULT="$("$AB" --session "$SESSION" get attr "$SEL" data-photo-bgremove-result 2>/dev/null | tr -d '"')"
|
|
156
|
+
[ -n "$RESULT" ] || { echo "photo-bg-remove.sh: harness reported done but no result asset" >&2; exit 4; }
|
|
157
|
+
|
|
158
|
+
echo "→ background removed → $RESULT" >&2
|
|
159
|
+
# Last stdout line = the new matte asset path, for $(maude design photo-bg-remove ...) capture.
|
|
160
|
+
printf '%s\n' "$RESULT"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# probe-footage.sh — thin shim over _probe-footage-playwright.mjs; reached via
|
|
3
|
+
# `maude design probe-footage` (DDR-062), never a raw bin path. Extracts keyframe
|
|
4
|
+
# PNGs from one clip in headless Chromium for the footage-analyst's vision pass
|
|
5
|
+
# (feature-footage-analysis-director, Task 4). See the .mjs for the rationale.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# probe-footage.sh <assets/<sha8>.<ext>> --root <repo> [--design-root .design]
|
|
9
|
+
# [--frames N] [--out-dir DIR]
|
|
10
|
+
#
|
|
11
|
+
# stdout on success = a JSON manifest { asset, durationSec, width, height, frames }.
|
|
12
|
+
# Exit: 0 ok · 2 usage · 3 no browser · 4 decode/probe error · 1 other.
|
|
13
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
14
|
+
|
|
15
|
+
case "$1" in
|
|
16
|
+
--help|-h) sed -n '2,14p' "$0" | sed 's/^# \?//'; exit 0 ;;
|
|
17
|
+
esac
|
|
18
|
+
|
|
19
|
+
# The probe imports `playwright` (Chromium). Prefer node (the runtime the other
|
|
20
|
+
# playwright shims run under); fall back to bun in a dev tree.
|
|
21
|
+
if command -v node >/dev/null 2>&1; then
|
|
22
|
+
exec node "$SCRIPT_DIR/_probe-footage-playwright.mjs" "$@"
|
|
23
|
+
elif command -v bun >/dev/null 2>&1; then
|
|
24
|
+
exec bun run "$SCRIPT_DIR/_probe-footage-playwright.mjs" "$@"
|
|
25
|
+
else
|
|
26
|
+
echo "probe-footage.sh: node or bun is required." >&2
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
@@ -105,8 +105,15 @@ Args:
|
|
|
105
105
|
--json No-op (JSON is the only output form).
|
|
106
106
|
|
|
107
107
|
FigJam v3 additive fields per annotation: z (render order), groupIds (deepest →
|
|
108
|
-
shallowest), author ("ai" for annotate-verb strokes),
|
|
109
|
-
bound arrows.
|
|
108
|
+
shallowest), author ("ai" for annotate-verb strokes), authorName/authorId
|
|
109
|
+
(human-drawn stickies), and from/to host ids on bound arrows.
|
|
110
|
+
|
|
111
|
+
Section annotations additionally carry members: an array of every OTHER
|
|
112
|
+
annotation whose center falls inside the section's rect, in SPATIAL reading
|
|
113
|
+
order (top-to-bottom, left-to-right — not paint order), each as
|
|
114
|
+
{ id, tool, order, x, y, w, h, href? }. Answers "what's in this section, and
|
|
115
|
+
in what order" for prompts like "make a video / Instagram carousel from
|
|
116
|
+
this section" without hand-computing containment.
|
|
110
117
|
|
|
111
118
|
Emits JSON to stdout. A missing annotation file emits [] (exit 0).`;
|
|
112
119
|
|
|
@@ -395,6 +402,31 @@ function readUnknown(tool, attrs, inner) {
|
|
|
395
402
|
// provenance, render order (z = document position). Applied uniformly after
|
|
396
403
|
// the per-tool extraction; arrows additionally surface bound endpoints as
|
|
397
404
|
// `from`/`to` host ids so a bound diagram reads back as a GRAPH.
|
|
405
|
+
const AUTHOR_NAME_MAX_LEN = 64;
|
|
406
|
+
|
|
407
|
+
// Phase 3 (whiteboard-improvements) — mirrors annotations-model.ts's
|
|
408
|
+
// sanitizeAuthorName (same code-point ranges: control chars, bidi
|
|
409
|
+
// overrides/isolates, zero-width chars, BOM) so a peer-synced
|
|
410
|
+
// `.annotations.svg`'s authorName/authorId reads identically through
|
|
411
|
+
// either surface. Every string this CLI returns is already treated as DATA
|
|
412
|
+
// by the whiteboard skill's trust model — this additionally strips the
|
|
413
|
+
// characters that could visually spoof an author name.
|
|
414
|
+
function sanitizeAuthorName(raw) {
|
|
415
|
+
let cleaned = '';
|
|
416
|
+
for (const ch of raw) {
|
|
417
|
+
const cp = ch.codePointAt(0) ?? 0;
|
|
418
|
+
const unsafe =
|
|
419
|
+
cp <= 0x1f ||
|
|
420
|
+
(cp >= 0x7f && cp <= 0x9f) ||
|
|
421
|
+
(cp >= 0x200b && cp <= 0x200f) ||
|
|
422
|
+
(cp >= 0x202a && cp <= 0x202e) ||
|
|
423
|
+
(cp >= 0x2066 && cp <= 0x2069) ||
|
|
424
|
+
cp === 0xfeff;
|
|
425
|
+
if (!unsafe) cleaned += ch;
|
|
426
|
+
}
|
|
427
|
+
return cleaned.trim().slice(0, AUTHOR_NAME_MAX_LEN);
|
|
428
|
+
}
|
|
429
|
+
|
|
398
430
|
function withShared(o, attrs, z) {
|
|
399
431
|
o.z = z;
|
|
400
432
|
const g = attr(attrs, 'data-group-ids');
|
|
@@ -403,6 +435,16 @@ function withShared(o, attrs, z) {
|
|
|
403
435
|
if (ids.length) o.groupIds = ids;
|
|
404
436
|
}
|
|
405
437
|
if (attr(attrs, 'data-author') === 'ai') o.author = 'ai';
|
|
438
|
+
const authorName = attr(attrs, 'data-author-name');
|
|
439
|
+
if (authorName) {
|
|
440
|
+
const cleaned = sanitizeAuthorName(decodeEntities(authorName));
|
|
441
|
+
if (cleaned) o.authorName = cleaned;
|
|
442
|
+
}
|
|
443
|
+
const authorId = attr(attrs, 'data-author-id');
|
|
444
|
+
if (authorId) {
|
|
445
|
+
const cleanedId = sanitizeAuthorName(decodeEntities(authorId));
|
|
446
|
+
if (cleanedId) o.authorId = cleanedId;
|
|
447
|
+
}
|
|
406
448
|
if (o.tool === 'arrow') {
|
|
407
449
|
const sb = parseBind(attr(attrs, 'data-start-bind'));
|
|
408
450
|
if (sb) o.from = sb.hostId;
|
|
@@ -615,6 +657,85 @@ function anchorToElement(ann, elements) {
|
|
|
615
657
|
};
|
|
616
658
|
}
|
|
617
659
|
|
|
660
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
661
|
+
// Section membership + reading order (Phase 5, feature-whiteboard-
|
|
662
|
+
// annotation-improvements). A common flow: the user drops several media/
|
|
663
|
+
// stickies INTO a section, then asks "make a video from this" / "turn this
|
|
664
|
+
// into an Instagram carousel" — the agent needs to know exactly which
|
|
665
|
+
// annotations live in a given section, and in what visual order, without
|
|
666
|
+
// hand-computing containment or guessing from raw x/y. Mirrors
|
|
667
|
+
// `findElement`'s smallest-containing-rect convention (annotation CENTER
|
|
668
|
+
// inside a rect), applied against SECTION rects instead of DOM element
|
|
669
|
+
// rects.
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Spatial reading order — top-to-bottom, then left-to-right — NOT `z`
|
|
673
|
+
* (paint/document order): a user can drop stickies in any order they like,
|
|
674
|
+
* and assembling a carousel/video in PAINT order would visibly scramble it.
|
|
675
|
+
* Rows are bucketed by a Y-tolerance band (half the smallest member's
|
|
676
|
+
* height, floored at 16 units) so items a few units off from each other
|
|
677
|
+
* still read as "the same row"; `z` only breaks a true tie (same band, same
|
|
678
|
+
* x) between two members.
|
|
679
|
+
*/
|
|
680
|
+
function attachSectionMembers(annotations) {
|
|
681
|
+
const sections = annotations.filter((a) => a.tool === 'section' && a.id);
|
|
682
|
+
if (!sections.length) return annotations;
|
|
683
|
+
|
|
684
|
+
const centerOf = (a) => {
|
|
685
|
+
if (a.x == null || a.y == null) return null;
|
|
686
|
+
return { cx: a.x + (a.w || 0) / 2, cy: a.y + (a.h || 0) / 2 };
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
const membersBySection = new Map();
|
|
690
|
+
for (const section of sections) {
|
|
691
|
+
const sx1 = section.x;
|
|
692
|
+
const sy1 = section.y;
|
|
693
|
+
const sx2 = section.x + (section.w || 0);
|
|
694
|
+
const sy2 = section.y + (section.h || 0);
|
|
695
|
+
const candidates = [];
|
|
696
|
+
for (const a of annotations) {
|
|
697
|
+
if (a.tool === 'section' || !a.id) continue;
|
|
698
|
+
const c = centerOf(a);
|
|
699
|
+
if (!c) continue;
|
|
700
|
+
if (c.cx < sx1 || c.cx > sx2 || c.cy < sy1 || c.cy > sy2) continue;
|
|
701
|
+
candidates.push({ ann: a, c });
|
|
702
|
+
}
|
|
703
|
+
if (!candidates.length) {
|
|
704
|
+
membersBySection.set(section.id, []);
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
const heights = candidates.map((m) => m.ann.h || 0).filter((h) => h > 0);
|
|
708
|
+
const minHeight = heights.length ? Math.min(...heights) : 40;
|
|
709
|
+
const band = Math.max(16, minHeight / 2);
|
|
710
|
+
candidates.sort((m1, m2) => {
|
|
711
|
+
const dy = m1.c.cy - m2.c.cy;
|
|
712
|
+
if (Math.abs(dy) > band) return dy;
|
|
713
|
+
const dx = m1.c.cx - m2.c.cx;
|
|
714
|
+
if (dx !== 0) return dx;
|
|
715
|
+
return (m1.ann.z ?? 0) - (m2.ann.z ?? 0);
|
|
716
|
+
});
|
|
717
|
+
membersBySection.set(
|
|
718
|
+
section.id,
|
|
719
|
+
candidates.map((m, i) => ({
|
|
720
|
+
id: m.ann.id,
|
|
721
|
+
tool: m.ann.tool,
|
|
722
|
+
order: i,
|
|
723
|
+
x: m.ann.x,
|
|
724
|
+
y: m.ann.y,
|
|
725
|
+
w: m.ann.w,
|
|
726
|
+
h: m.ann.h,
|
|
727
|
+
...(m.ann.href ? { href: m.ann.href } : null),
|
|
728
|
+
}))
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
return annotations.map((a) =>
|
|
733
|
+
a.tool === 'section' && membersBySection.has(a.id)
|
|
734
|
+
? { ...a, members: membersBySection.get(a.id) }
|
|
735
|
+
: a
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
|
|
618
739
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
619
740
|
// Main
|
|
620
741
|
|
|
@@ -674,6 +795,7 @@ function main() {
|
|
|
674
795
|
if (artboards.length) {
|
|
675
796
|
annotations = annotations.map((a) => anchorToArtboard(a, artboards));
|
|
676
797
|
}
|
|
798
|
+
annotations = attachSectionMembers(annotations);
|
|
677
799
|
|
|
678
800
|
if (args.graph) {
|
|
679
801
|
process.stdout.write(`${JSON.stringify({ annotations, graph: buildGraph(annotations) })}\n`);
|
|
@@ -689,6 +811,7 @@ function main() {
|
|
|
689
811
|
// canvas-rects manifest without a second implementation.
|
|
690
812
|
export {
|
|
691
813
|
anchorToElement,
|
|
814
|
+
attachSectionMembers,
|
|
692
815
|
buildGraph,
|
|
693
816
|
fileSlug,
|
|
694
817
|
findElement,
|
|
@@ -9,12 +9,18 @@
|
|
|
9
9
|
# [--screen <id> | --element <id> | --selector <css> | --full]
|
|
10
10
|
# [--all-screens] [--out <path>] [--out-dir <dir>]
|
|
11
11
|
# [--timeout 8] [--engine auto|agent-browser|playwright]
|
|
12
|
-
# [--root <repo>]
|
|
12
|
+
# [--theme <name>] [--root <repo>]
|
|
13
13
|
#
|
|
14
14
|
# Notes:
|
|
15
15
|
# - Exactly one of --screen / --element / --selector / --full is required
|
|
16
16
|
# (or --all-screens which loops over every [data-dc-screen]/[data-dc-slot]).
|
|
17
17
|
# - --out required for single-shot modes; --out-dir required for --all-screens.
|
|
18
|
+
# - --theme <name> forces every `[data-theme]` element (DS artboard wrappers)
|
|
19
|
+
# to that value BEFORE capture, via a DOM eval — does not touch the actual
|
|
20
|
+
# canvas file or the running server's state. Used to capture a DS's
|
|
21
|
+
# alternate theme deterministically for the dual-theme reality check
|
|
22
|
+
# (/design:new step 9) instead of relying on whatever the canvas is pinned
|
|
23
|
+
# to. No-op if the canvas has no `[data-theme]` elements.
|
|
18
24
|
# - URL resolution: --url > --port + _active.json > _server.json + _active.json.
|
|
19
25
|
# - Stdout: written PNG paths, one per line (composable in for-loops).
|
|
20
26
|
# - Stderr: diagnostic, engine choice, timing.
|
|
@@ -30,6 +36,7 @@ TIMEOUT=8
|
|
|
30
36
|
ENGINE="auto"
|
|
31
37
|
ALL_SCREENS=0
|
|
32
38
|
ROOT=""
|
|
39
|
+
THEME=""
|
|
33
40
|
|
|
34
41
|
while [ $# -gt 0 ]; do
|
|
35
42
|
case "$1" in
|
|
@@ -44,9 +51,10 @@ while [ $# -gt 0 ]; do
|
|
|
44
51
|
--out-dir) OUT_DIR="$2"; shift 2 ;;
|
|
45
52
|
--timeout) TIMEOUT="$2"; shift 2 ;;
|
|
46
53
|
--engine) ENGINE="$2"; shift 2 ;;
|
|
54
|
+
--theme) THEME="$2"; shift 2 ;;
|
|
47
55
|
--root) ROOT="$2"; shift 2 ;;
|
|
48
56
|
--help|-h)
|
|
49
|
-
sed -n '2,
|
|
57
|
+
sed -n '2,27p' "$0" | sed 's/^# \?//'
|
|
50
58
|
exit 0
|
|
51
59
|
;;
|
|
52
60
|
*)
|
|
@@ -57,6 +65,20 @@ while [ $# -gt 0 ]; do
|
|
|
57
65
|
done
|
|
58
66
|
|
|
59
67
|
# ---------- arg validation ----------
|
|
68
|
+
# --theme ultimately traces back to config.json's designSystems[].themes[]
|
|
69
|
+
# (untrusted, same posture as prep.sh's MOODBOARD_VARIANTS). Callers (e.g.
|
|
70
|
+
# new.md step 9) build an --out-dir path from this value — reject anything
|
|
71
|
+
# outside a safe slug charset here too, at the shared sink, rather than
|
|
72
|
+
# trusting every caller to re-derive this guard.
|
|
73
|
+
if [ -n "$THEME" ]; then
|
|
74
|
+
case "$THEME" in
|
|
75
|
+
*[!A-Za-z0-9._-]*|"")
|
|
76
|
+
echo "screenshot.sh: --theme '$THEME' contains unsafe characters (allowed: A-Za-z0-9._-)" >&2
|
|
77
|
+
exit 2
|
|
78
|
+
;;
|
|
79
|
+
esac
|
|
80
|
+
fi
|
|
81
|
+
|
|
60
82
|
if [ $ALL_SCREENS -eq 1 ]; then
|
|
61
83
|
[ -z "$OUT_DIR" ] && { echo "screenshot.sh: --all-screens needs --out-dir" >&2; exit 2; }
|
|
62
84
|
else
|
|
@@ -189,10 +211,12 @@ pw_screenshot() {
|
|
|
189
211
|
return 1
|
|
190
212
|
fi
|
|
191
213
|
echo "→ playwright engine; first invocation may install chromium (~150MB, one-off)" >&2
|
|
214
|
+
local theme_args=()
|
|
215
|
+
[ -n "$THEME" ] && theme_args=(--theme "$THEME")
|
|
192
216
|
if [ -n "$css" ]; then
|
|
193
|
-
npm exec --yes --package=playwright -- node "$pw_script" --url "$URL" --selector "$css" --out "$out" --timeout "$TIMEOUT" >&2 || return 1
|
|
217
|
+
npm exec --yes --package=playwright -- node "$pw_script" --url "$URL" --selector "$css" --out "$out" --timeout "$TIMEOUT" "${theme_args[@]}" >&2 || return 1
|
|
194
218
|
else
|
|
195
|
-
npm exec --yes --package=playwright -- node "$pw_script" --url "$URL" --out "$out" --timeout "$TIMEOUT" >&2 || return 1
|
|
219
|
+
npm exec --yes --package=playwright -- node "$pw_script" --url "$URL" --out "$out" --timeout "$TIMEOUT" "${theme_args[@]}" >&2 || return 1
|
|
196
220
|
fi
|
|
197
221
|
[ -s "$out" ] || { echo "✗ playwright wrote no file: $out" >&2; return 1; }
|
|
198
222
|
return 0
|
|
@@ -224,9 +248,34 @@ navigate_once() {
|
|
|
224
248
|
echo "→ no DC mount detected after ${TIMEOUT}s — proceeding (may be a non-canvas page)" >&2
|
|
225
249
|
sleep 1
|
|
226
250
|
fi
|
|
251
|
+
apply_theme_override
|
|
227
252
|
fi
|
|
228
253
|
}
|
|
229
254
|
|
|
255
|
+
# ---------- --theme override (dual-theme reality check) ----------
|
|
256
|
+
# Forces every element carrying a `data-theme` attribute (DS artboard theme
|
|
257
|
+
# wrappers) to $THEME, so the caller can deterministically capture a DS's
|
|
258
|
+
# alternate theme instead of whatever the canvas happens to be pinned to.
|
|
259
|
+
# agent-browser only — the playwright path applies it inside
|
|
260
|
+
# _screenshot-playwright.mjs (passed via --theme) since that shim owns its own
|
|
261
|
+
# page.evaluate. No-op when --theme wasn't passed or no [data-theme] elements
|
|
262
|
+
# exist (e.g. a single-theme DS or a non-canvas page).
|
|
263
|
+
apply_theme_override() {
|
|
264
|
+
[ -z "$THEME" ] && return 0
|
|
265
|
+
[ "$ENGINE" = "agent-browser" ] || return 0
|
|
266
|
+
# THEME ultimately traces back to config.json's designSystems[].themes[]
|
|
267
|
+
# array — treated as untrusted input elsewhere in this codebase (see
|
|
268
|
+
# prep.sh's MOODBOARD_VARIANTS clamp). JSON-encode it via jq so it lands as
|
|
269
|
+
# a properly-escaped JS string literal in the eval'd expression below,
|
|
270
|
+
# instead of naively splicing the raw value into a JS string (which a
|
|
271
|
+
# crafted config value could break out of).
|
|
272
|
+
local theme_json
|
|
273
|
+
theme_json=$(jq -Rn --arg t "$THEME" '$t' 2>/dev/null) || return 0
|
|
274
|
+
local n
|
|
275
|
+
n=$("$AB" eval "(function(t){var els=document.querySelectorAll('[data-theme]');els.forEach(function(el){el.setAttribute('data-theme',t)});return els.length})($theme_json)" 2>/dev/null)
|
|
276
|
+
echo "→ theme override: forced data-theme=\"$THEME\" on $(printf '%s' "$n" | tr -d '[:space:]') element(s)" >&2
|
|
277
|
+
}
|
|
278
|
+
|
|
230
279
|
capture() {
|
|
231
280
|
local css="$1"
|
|
232
281
|
local out="$2"
|