@1agh/maude 1.0.11 → 1.3.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 +3 -1
- package/apps/studio/acp/bridge.ts +18 -13
- package/apps/studio/acp/index.ts +43 -7
- package/apps/studio/acp/transcript-io.ts +394 -0
- package/apps/studio/acp/transcript.ts +103 -38
- package/apps/studio/annotations-layer.tsx +191 -116
- package/apps/studio/annotations-model.ts +39 -0
- package/apps/studio/annotations-sync.ts +50 -0
- package/apps/studio/api.ts +821 -55
- package/apps/studio/bin/_import-figma.mjs +57 -11
- package/apps/studio/bin/_seed-fixture.mjs +163 -0
- package/apps/studio/bin/annotate.mjs +3 -1
- package/apps/studio/bin/server-up.sh +17 -2
- package/apps/studio/canvas-edit.ts +391 -7
- package/apps/studio/canvas-imports.ts +89 -0
- package/apps/studio/canvas-lib.tsx +82 -18
- package/apps/studio/canvas-notice-message.ts +16 -0
- package/apps/studio/canvas-notifications.tsx +19 -0
- package/apps/studio/canvas-shell.tsx +57 -6
- package/apps/studio/client/app.jsx +943 -248
- package/apps/studio/client/apply-edit-request.ts +55 -0
- package/apps/studio/client/export-center.jsx +125 -67
- package/apps/studio/client/github.js +38 -1
- package/apps/studio/client/index-loader.ts +76 -0
- package/apps/studio/client/panels/CloudBar.jsx +52 -6
- package/apps/studio/client/panels/GitPanel.jsx +103 -0
- package/apps/studio/client/panels/OnboardingWizard.jsx +36 -7
- package/apps/studio/client/panels/RepoBranchSwitcher.jsx +92 -27
- package/apps/studio/client/panels/SourceConflictPanel.jsx +166 -0
- package/apps/studio/client/panels/SyncPanel.jsx +238 -4
- package/apps/studio/client/panels/TeamProjects.jsx +376 -0
- package/apps/studio/client/panels/file-deep-link-dialog.jsx +54 -0
- package/apps/studio/client/photo-knobs.jsx +3 -0
- package/apps/studio/client/report-bug.jsx +19 -1
- package/apps/studio/client/share-dialog.jsx +91 -0
- package/apps/studio/client/share-link.js +94 -0
- package/apps/studio/client/styles/3-shell-maude.css +69 -18
- package/apps/studio/client/styles/4-components.css +44 -1
- package/apps/studio/client/tree-row-menu.jsx +48 -3
- package/apps/studio/client/whats-new.jsx +26 -32
- package/apps/studio/cloud/endpoints.ts +132 -0
- package/apps/studio/collab/index.ts +4 -0
- package/apps/studio/collab/persistence.ts +137 -17
- package/apps/studio/collab/registry.ts +19 -21
- package/apps/studio/collab/room.ts +13 -2
- package/apps/studio/context.ts +59 -0
- package/apps/studio/debug-bundle.ts +13 -0
- package/apps/studio/dist/client.bundle.js +1911 -1879
- package/apps/studio/dist/runtime/.min-sizes.json +1 -0
- package/apps/studio/dist/runtime/sonner.js +1 -0
- package/apps/studio/dist/styles.css +1 -1
- package/apps/studio/export-dialog.tsx +54 -0
- package/apps/studio/exporters/degraded.ts +19 -3
- package/apps/studio/exporters/ghostscript.ts +194 -0
- package/apps/studio/exporters/pdf-fonts.ts +261 -0
- package/apps/studio/exporters/pdf.ts +152 -3
- package/apps/studio/exporters/remote.ts +36 -1
- package/apps/studio/fs-watch.ts +45 -1
- package/apps/studio/http.ts +296 -19
- package/apps/studio/inspect.ts +19 -0
- package/apps/studio/managed-projects.ts +150 -0
- package/apps/studio/mem.ts +45 -9
- package/apps/studio/notifications.tsx +292 -0
- package/apps/studio/runtime-bundle.ts +2 -0
- package/apps/studio/server.ts +69 -6
- package/apps/studio/sync/accepted-cold-start.ts +225 -0
- package/apps/studio/sync/accepted-link.ts +320 -0
- package/apps/studio/sync/action-stage.ts +343 -0
- package/apps/studio/sync/agent.ts +104 -45
- package/apps/studio/sync/asset-push.ts +1 -47
- package/apps/studio/sync/cell-file-events.ts +3 -0
- package/apps/studio/sync/codec.ts +192 -34
- package/apps/studio/sync/cold-start-apply.ts +7 -0
- package/apps/studio/sync/cold-start.ts +118 -22
- package/apps/studio/sync/comment-identity.ts +68 -0
- package/apps/studio/sync/connection-state.ts +15 -1
- package/apps/studio/sync/ctl-provider.ts +15 -2
- package/apps/studio/sync/document-discovery.ts +81 -0
- package/apps/studio/sync/file-ledger.ts +117 -0
- package/apps/studio/sync/file-membership.ts +10 -2
- package/apps/studio/sync/file-plane.ts +926 -47
- package/apps/studio/sync/index.ts +1786 -36
- package/apps/studio/sync/journal-client.ts +13 -1
- package/apps/studio/sync/migrate-seed.ts +150 -22
- package/apps/studio/sync/poke.ts +4 -2
- package/apps/studio/sync/presentation.ts +311 -5
- package/apps/studio/sync/projection.ts +820 -23
- package/apps/studio/sync/repeated-module.ts +73 -0
- package/apps/studio/sync/retry-after.ts +169 -0
- package/apps/studio/sync/revision-barrier.ts +129 -0
- package/apps/studio/sync/seed-progress.ts +218 -0
- package/apps/studio/sync/seed-repair.ts +46 -0
- package/apps/studio/sync/source-merge.ts +100 -0
- package/apps/studio/sync/source-ops.ts +289 -0
- package/apps/studio/sync/source-recovery.ts +70 -0
- package/apps/studio/sync/source-validation.ts +56 -0
- package/apps/studio/sync/status.ts +201 -6
- package/apps/studio/sync/transaction-client.ts +460 -0
- package/apps/studio/sync/transport-error.ts +111 -0
- package/apps/studio/sync/writer-registry.ts +236 -0
- package/apps/studio/text-caret.ts +35 -0
- package/apps/studio/undo-hud.tsx +9 -87
- package/apps/studio/use-canvas-media-drop.tsx +4 -39
- package/apps/studio/whats-new.json +173 -0
- package/cli/bin/maude.mjs +2 -0
- package/cli/commands/codex.mjs +191 -0
- package/cli/commands/harness.mjs +1051 -0
- package/cli/commands/help.mjs +13 -0
- package/cli/lib/design-link.mjs +190 -2
- package/cli/lib/harness/capabilities.mjs +302 -0
- package/cli/lib/harness/codex-runtime.mjs +892 -0
- package/cli/lib/harness/compatibility.mjs +27 -0
- package/cli/lib/harness/discover-claude.mjs +1393 -0
- package/cli/lib/harness/managed-state.mjs +1296 -0
- package/cli/lib/harness/model.mjs +86 -0
- package/cli/lib/harness/secrets.mjs +390 -0
- package/cli/lib/harness/targets/codex.mjs +1141 -0
- package/cli/lib/harness/targets/opencode.mjs +807 -0
- package/cli/lib/harness/transaction.mjs +1099 -0
- package/cli/lib/hubs-config.mjs +35 -8
- package/cli/templates/harness/opencode/maude-projector.ts +58 -0
- package/docs/harness-capability-matrix.md +125 -0
- package/docs/harness-environment-projection.md +95 -0
- package/package.json +33 -9
- package/plugins/design/dependencies.json +3 -3
- package/plugins/design/templates/_shell.html +47 -0
- package/plugins/flow/.claude-plugin/config.schema.json +8 -0
- package/plugins/flow/dependencies.json +3 -3
- package/apps/studio/.ai/cache/_stats.json +0 -7
- package/apps/studio/bin/_agent-browser-safe.test.mjs +0 -165
- package/apps/studio/bin/_curl-local.test.mjs +0 -280
- package/apps/studio/bin/_pw-launch.test.mjs +0 -90
- package/apps/studio/bin/_smart-frames.test.mjs +0 -195
- package/apps/studio/bin/_transcribe.test.mjs +0 -80
- package/apps/studio/bun.lock +0 -928
- package/apps/studio/draw/test/animate.test.ts +0 -172
- package/apps/studio/draw/test/brush.test.ts +0 -213
- package/apps/studio/draw/test/composition.test.ts +0 -141
- package/apps/studio/draw/test/geometry.test.ts +0 -199
- package/apps/studio/draw/test/gradient.test.ts +0 -167
- package/apps/studio/draw/test/layout.test.ts +0 -120
- package/apps/studio/draw/test/morph.test.ts +0 -107
- package/apps/studio/draw/test/optimize.test.ts +0 -40
- package/apps/studio/draw/test/palette.test.ts +0 -123
- package/apps/studio/draw/test/primitives.test.ts +0 -129
- package/apps/studio/draw/test/serialize-animate.test.ts +0 -142
- package/apps/studio/draw/test/serialize.test.ts +0 -105
- package/apps/studio/figma/assets.test.ts +0 -464
- package/apps/studio/figma/client.test.ts +0 -395
- package/apps/studio/figma/codegen-client.test.ts +0 -276
- package/apps/studio/figma/codegen-fonts.test.ts +0 -103
- package/apps/studio/figma/codegen-values.test.ts +0 -179
- package/apps/studio/figma/comments-to-strokes.test.ts +0 -194
- package/apps/studio/figma/fig-decode.test.ts +0 -788
- package/apps/studio/figma/fig-differential.test.ts +0 -182
- package/apps/studio/figma/fig-translator.test.ts +0 -192
- package/apps/studio/figma/fig-vector.test.ts +0 -113
- package/apps/studio/figma/from-codegen.test.ts +0 -408
- package/apps/studio/figma/sanitize.test.ts +0 -325
- package/apps/studio/figma/tailwind-map.test.ts +0 -142
- package/apps/studio/figma/to-artboard.test.ts +0 -808
- package/apps/studio/figma/to-render.test.ts +0 -180
- package/apps/studio/figma/to-strokes-roundtrip.test.ts +0 -152
- package/apps/studio/figma/to-strokes.test.ts +0 -705
- package/apps/studio/figma/to-tokens.test.ts +0 -321
- package/apps/studio/figma/url.test.ts +0 -167
- package/apps/studio/footage/schema.test.ts +0 -287
- package/apps/studio/generation/adapters/groq.test.ts +0 -80
- package/apps/studio/generation/audio-library.test.ts +0 -95
- package/apps/studio/generation/captions.test.ts +0 -78
- package/apps/studio/generation/elevenlabs.test.ts +0 -223
- package/apps/studio/generation/gemini.test.ts +0 -350
- package/apps/studio/generation/jobs.test.ts +0 -128
- package/apps/studio/generation/keys.test.ts +0 -75
- package/apps/studio/generation/prefs.test.ts +0 -95
- package/apps/studio/generation/types.test.ts +0 -123
- package/apps/studio/generation/whisper-models.test.ts +0 -179
- package/apps/studio/test/_helpers.ts +0 -108
- package/apps/studio/test/acp-activity-endpoint.test.ts +0 -183
- package/apps/studio/test/acp-activity.test.ts +0 -154
- package/apps/studio/test/acp-ai-activity.test.ts +0 -182
- package/apps/studio/test/acp-attachment-serve.test.ts +0 -74
- package/apps/studio/test/acp-bootstrap-brief.test.ts +0 -178
- package/apps/studio/test/acp-branch-guard.test.ts +0 -123
- package/apps/studio/test/acp-bridge-lifetime.test.ts +0 -475
- package/apps/studio/test/acp-bridge.test.ts +0 -545
- package/apps/studio/test/acp-capabilities.test.ts +0 -123
- package/apps/studio/test/acp-caps-bridge.test.ts +0 -279
- package/apps/studio/test/acp-commands.test.ts +0 -119
- package/apps/studio/test/acp-elicitation-bridge.test.ts +0 -480
- package/apps/studio/test/acp-elicitation.test.ts +0 -251
- package/apps/studio/test/acp-env.test.ts +0 -95
- package/apps/studio/test/acp-mode-banner.test.ts +0 -45
- package/apps/studio/test/acp-origin-gate.test.ts +0 -160
- package/apps/studio/test/acp-permission-prompt.test.ts +0 -251
- package/apps/studio/test/acp-permission.test.ts +0 -278
- package/apps/studio/test/acp-plugin-bootstrap.test.ts +0 -89
- package/apps/studio/test/acp-session-allowed-tools.test.ts +0 -235
- package/apps/studio/test/acp-session-plugins.test.ts +0 -209
- package/apps/studio/test/acp-toolgroup.test.ts +0 -76
- package/apps/studio/test/acp-transcript-view.test.ts +0 -71
- package/apps/studio/test/acp-transcript.test.ts +0 -238
- package/apps/studio/test/acp-usage-bridge.test.ts +0 -141
- package/apps/studio/test/acp-usage.test.ts +0 -143
- package/apps/studio/test/acp-write-gate.test.ts +0 -323
- package/apps/studio/test/acp-write-scope.test.ts +0 -533
- package/apps/studio/test/active-state.test.ts +0 -186
- package/apps/studio/test/activity.test.ts +0 -189
- package/apps/studio/test/ai-activity.test.ts +0 -113
- package/apps/studio/test/annotate-write.test.ts +0 -1074
- package/apps/studio/test/annotation-strokes-command.test.ts +0 -166
- package/apps/studio/test/annotations-align.test.ts +0 -88
- package/apps/studio/test/annotations-api.test.ts +0 -145
- package/apps/studio/test/annotations-bindings.test.ts +0 -262
- package/apps/studio/test/annotations-draw-modifiers.test.ts +0 -206
- package/apps/studio/test/annotations-groups.test.ts +0 -231
- package/apps/studio/test/annotations-layer.test.ts +0 -1011
- package/apps/studio/test/annotations-roundtrip.test.ts +0 -663
- package/apps/studio/test/annotations-snap.test.ts +0 -135
- package/apps/studio/test/annotations-text-format.test.ts +0 -396
- package/apps/studio/test/artboard-activity-overlay.test.tsx +0 -56
- package/apps/studio/test/artboard-guides-overlay.test.tsx +0 -152
- package/apps/studio/test/artboard-hug-commit.test.ts +0 -36
- package/apps/studio/test/artboard-kinds.test.tsx +0 -83
- package/apps/studio/test/artboard-selection-attrs.test.ts +0 -69
- package/apps/studio/test/asset-api.test.ts +0 -149
- package/apps/studio/test/assets-s3.test.ts +0 -249
- package/apps/studio/test/binary-smoke.test.ts +0 -47
- package/apps/studio/test/boot-self-heal.test.ts +0 -87
- package/apps/studio/test/browse-posture.test.tsx +0 -107
- package/apps/studio/test/bundle-smoke.test.ts +0 -46
- package/apps/studio/test/camera-reveal.test.tsx +0 -173
- package/apps/studio/test/canvas-artifacts.test.ts +0 -125
- package/apps/studio/test/canvas-build-sandbox.test.ts +0 -69
- package/apps/studio/test/canvas-build.test.ts +0 -141
- package/apps/studio/test/canvas-create-api.test.ts +0 -676
- package/apps/studio/test/canvas-cursors.test.ts +0 -164
- package/apps/studio/test/canvas-edit.test.ts +0 -470
- package/apps/studio/test/canvas-freshness-deps.test.ts +0 -64
- package/apps/studio/test/canvas-header.test.ts +0 -127
- package/apps/studio/test/canvas-hide-chrome.test.ts +0 -88
- package/apps/studio/test/canvas-hmr-runtime.test.tsx +0 -114
- package/apps/studio/test/canvas-lib-inline.test.ts +0 -146
- package/apps/studio/test/canvas-lib-motion.test.ts +0 -131
- package/apps/studio/test/canvas-lib-resolver.test.ts +0 -111
- package/apps/studio/test/canvas-list-watch.test.ts +0 -388
- package/apps/studio/test/canvas-media-drop.test.ts +0 -187
- package/apps/studio/test/canvas-meta-api.test.ts +0 -707
- package/apps/studio/test/canvas-move-api.test.ts +0 -587
- package/apps/studio/test/canvas-origin-gate.test.ts +0 -417
- package/apps/studio/test/canvas-pipeline.test.ts +0 -180
- package/apps/studio/test/canvas-rects.test.ts +0 -198
- package/apps/studio/test/canvas-reorder.test.ts +0 -211
- package/apps/studio/test/canvas-route.test.ts +0 -179
- package/apps/studio/test/canvas-shell-base.test.ts +0 -107
- package/apps/studio/test/canvas-shell-build-error.test.ts +0 -52
- package/apps/studio/test/canvas-url.test.ts +0 -138
- package/apps/studio/test/canvas-wheel-delta.test.ts +0 -42
- package/apps/studio/test/canvas-zoom-floor.test.ts +0 -73
- package/apps/studio/test/capture-core.test.ts +0 -32
- package/apps/studio/test/capture-determinism-shape.test.ts +0 -135
- package/apps/studio/test/chat-attachments.test.ts +0 -117
- package/apps/studio/test/chat-context.test.ts +0 -129
- package/apps/studio/test/chat-markdown.test.tsx +0 -58
- package/apps/studio/test/clip-addressing.test.ts +0 -738
- package/apps/studio/test/clip-ops.test.ts +0 -922
- package/apps/studio/test/cloud-build.test.ts +0 -95
- package/apps/studio/test/cloud-connect-note.test.ts +0 -135
- package/apps/studio/test/cloud-endpoints.test.ts +0 -590
- package/apps/studio/test/cloud-history-hardening.test.ts +0 -165
- package/apps/studio/test/cloud-history-posture.test.ts +0 -230
- package/apps/studio/test/cloud-managed-save-surfaces.test.ts +0 -90
- package/apps/studio/test/cloud-renew.test.ts +0 -205
- package/apps/studio/test/cloud-session-role.test.ts +0 -161
- package/apps/studio/test/cloud-shell-surfaces.test.ts +0 -170
- package/apps/studio/test/cold-start-apply.test.ts +0 -303
- package/apps/studio/test/collab-annotations-bridge.test.ts +0 -55
- package/apps/studio/test/collab-awareness-bridge.test.ts +0 -223
- package/apps/studio/test/collab-bridge.test.ts +0 -79
- package/apps/studio/test/collab-loopback.test.ts +0 -63
- package/apps/studio/test/collab-origin-gate.test.ts +0 -323
- package/apps/studio/test/collab-protocol.test.ts +0 -146
- package/apps/studio/test/collab-readonly-gate.test.ts +0 -194
- package/apps/studio/test/collab-reseed-guard.test.ts +0 -78
- package/apps/studio/test/collab-room.test.ts +0 -188
- package/apps/studio/test/collab-session-survival.test.tsx +0 -176
- package/apps/studio/test/collab-stress.test.ts +0 -129
- package/apps/studio/test/comment-mount.test.ts +0 -97
- package/apps/studio/test/comment-relay-origin-gate.test.ts +0 -117
- package/apps/studio/test/comments-api.test.ts +0 -277
- package/apps/studio/test/comments-fs-rebroadcast.test.ts +0 -155
- package/apps/studio/test/comments-overlay.test.ts +0 -117
- package/apps/studio/test/compile-entry.test.ts +0 -133
- package/apps/studio/test/component-map.test.ts +0 -48
- package/apps/studio/test/config-projection.test.ts +0 -104
- package/apps/studio/test/config-reload.test.ts +0 -230
- package/apps/studio/test/config-version.test.ts +0 -88
- package/apps/studio/test/context-resolve-tokens.test.ts +0 -97
- package/apps/studio/test/convert-to-absolute.test.ts +0 -333
- package/apps/studio/test/credential-grammar.test.ts +0 -107
- package/apps/studio/test/csp-canvas-shell.test.ts +0 -62
- package/apps/studio/test/csrf-write-guard.test.ts +0 -141
- package/apps/studio/test/debug-bundle.test.ts +0 -105
- package/apps/studio/test/design-setup-readiness.test.ts +0 -155
- package/apps/studio/test/detach-component.test.ts +0 -94
- package/apps/studio/test/dns-rebinding-guard.test.ts +0 -101
- package/apps/studio/test/dom-selection.test.ts +0 -198
- package/apps/studio/test/dynamic-text-edit.test.ts +0 -162
- package/apps/studio/test/edit-css-occurrence.test.ts +0 -81
- package/apps/studio/test/edit-persistence.test.ts +0 -91
- package/apps/studio/test/edit-scope-api.test.ts +0 -119
- package/apps/studio/test/edit-source-command.test.ts +0 -142
- package/apps/studio/test/edit-suppress.test.ts +0 -170
- package/apps/studio/test/editing-presence.test.ts +0 -103
- package/apps/studio/test/element-resize.test.ts +0 -136
- package/apps/studio/test/element-structural-api.test.ts +0 -434
- package/apps/studio/test/element-structural-edit.test.ts +0 -750
- package/apps/studio/test/ensure-browser.test.ts +0 -92
- package/apps/studio/test/equal-spacing-detector.test.ts +0 -257
- package/apps/studio/test/export-assemble.test.ts +0 -53
- package/apps/studio/test/export-browser-lane.test.ts +0 -126
- package/apps/studio/test/export-capture-fidelity.test.ts +0 -146
- package/apps/studio/test/export-capture-hygiene.test.ts +0 -675
- package/apps/studio/test/export-center.test.tsx +0 -287
- package/apps/studio/test/export-e2e-lanes.test.ts +0 -857
- package/apps/studio/test/export-format-scope-coherence.test.ts +0 -89
- package/apps/studio/test/export-lane.test.ts +0 -245
- package/apps/studio/test/export-shim-multi-capture.test.ts +0 -83
- package/apps/studio/test/exporters/canva.test.ts +0 -63
- package/apps/studio/test/exporters/degraded-propagation.test.ts +0 -123
- package/apps/studio/test/exporters/endpoint.test.ts +0 -121
- package/apps/studio/test/exporters/history.test.ts +0 -107
- package/apps/studio/test/exporters/html.test.ts +0 -26
- package/apps/studio/test/exporters/jobs.test.ts +0 -342
- package/apps/studio/test/exporters/pdf.test.ts +0 -85
- package/apps/studio/test/exporters/png.test.ts +0 -103
- package/apps/studio/test/exporters/pptx-deck.test.ts +0 -112
- package/apps/studio/test/exporters/pptx.test.ts +0 -31
- package/apps/studio/test/exporters/pw-launch.test.ts +0 -49
- package/apps/studio/test/exporters/runtime.test.ts +0 -59
- package/apps/studio/test/exporters/scope.test.ts +0 -292
- package/apps/studio/test/exporters/svg.test.ts +0 -29
- package/apps/studio/test/exporters/unsupported-media.test.ts +0 -123
- package/apps/studio/test/exporters/zip.test.ts +0 -104
- package/apps/studio/test/fetch-asset-gate.test.ts +0 -189
- package/apps/studio/test/figjam-v3-model.test.ts +0 -342
- package/apps/studio/test/figma-explode.test.ts +0 -438
- package/apps/studio/test/figma-provenance.test.ts +0 -108
- package/apps/studio/test/figma-routes.test.ts +0 -294
- package/apps/studio/test/file-lock.test.ts +0 -84
- package/apps/studio/test/fixtures/backgrounding-claude-auth.sh +0 -12
- package/apps/studio/test/fixtures/fake-claude-auth.mjs +0 -23
- package/apps/studio/test/fixtures/figjam-v3-groups-bindings.svg +0 -1
- package/apps/studio/test/fixtures/mock-acp-agent-caps.mjs +0 -158
- package/apps/studio/test/fixtures/mock-acp-agent-commands.mjs +0 -44
- package/apps/studio/test/fixtures/mock-acp-agent-elicit-flood.mjs +0 -46
- package/apps/studio/test/fixtures/mock-acp-agent-elicit-url.mjs +0 -42
- package/apps/studio/test/fixtures/mock-acp-agent-elicit.mjs +0 -63
- package/apps/studio/test/fixtures/mock-acp-agent-permission-flood.mjs +0 -51
- package/apps/studio/test/fixtures/mock-acp-agent-permission.mjs +0 -50
- package/apps/studio/test/fixtures/mock-acp-agent-slow.mjs +0 -45
- package/apps/studio/test/fixtures/mock-acp-agent-usage.mjs +0 -56
- package/apps/studio/test/fixtures/mock-acp-agent-wedged.mjs +0 -37
- package/apps/studio/test/fixtures/mock-acp-agent-write.mjs +0 -118
- package/apps/studio/test/fixtures/mock-acp-agent.mjs +0 -69
- package/apps/studio/test/fixtures/noisy-claude-auth.mjs +0 -27
- package/apps/studio/test/fixtures/perf-canvas.mjs +0 -201
- package/apps/studio/test/fixtures/phase-20-annotations.svg +0 -1
- package/apps/studio/test/fixtures/phase-21-annotations.svg +0 -1
- package/apps/studio/test/fixtures/tiny-subset.woff2 +0 -0
- package/apps/studio/test/fixtures/video-comp-fixture.tsx +0 -82
- package/apps/studio/test/fixtures/wedged-claude-auth.sh +0 -11
- package/apps/studio/test/footage-store.test.ts +0 -100
- package/apps/studio/test/fs-mkdir-api.test.ts +0 -248
- package/apps/studio/test/fs-watch.test.ts +0 -41
- package/apps/studio/test/gemma-models.test.ts +0 -245
- package/apps/studio/test/generate-route.test.ts +0 -106
- package/apps/studio/test/git-api.test.ts +0 -0
- package/apps/studio/test/git-branches.test.ts +0 -341
- package/apps/studio/test/git-cloud-posture.test.ts +0 -59
- package/apps/studio/test/git-grouping.test.ts +0 -106
- package/apps/studio/test/git-lifecycle.test.ts +0 -100
- package/apps/studio/test/git-log-format.test.ts +0 -95
- package/apps/studio/test/git-safe-rel.test.ts +0 -132
- package/apps/studio/test/git-system-engine.test.ts +0 -135
- package/apps/studio/test/git-watch.test.ts +0 -97
- package/apps/studio/test/github-api.test.ts +0 -577
- package/apps/studio/test/grid-track-handles.test.ts +0 -160
- package/apps/studio/test/handoff-static-frames.test.ts +0 -215
- package/apps/studio/test/handoff.test.ts +0 -329
- package/apps/studio/test/history-rollback.test.ts +0 -88
- package/apps/studio/test/hmr-broadcast.test.ts +0 -236
- package/apps/studio/test/hmr-classify.test.ts +0 -87
- package/apps/studio/test/hub-link.test.ts +0 -80
- package/apps/studio/test/identity-cache.test.ts +0 -83
- package/apps/studio/test/import-asset-browser.test.ts +0 -64
- package/apps/studio/test/import-asset-route.test.ts +0 -71
- package/apps/studio/test/import-asset.test.ts +0 -427
- package/apps/studio/test/import-brand-browser.test.ts +0 -88
- package/apps/studio/test/import-brand-route.test.ts +0 -120
- package/apps/studio/test/import-brand.test.ts +0 -206
- package/apps/studio/test/import-figma.test.ts +0 -667
- package/apps/studio/test/import-tokens.test.ts +0 -722
- package/apps/studio/test/index-data-canvas-ds.test.ts +0 -97
- package/apps/studio/test/input-router.test.ts +0 -757
- package/apps/studio/test/inspect-script-syntax.test.ts +0 -53
- package/apps/studio/test/inspect-selections.test.ts +0 -219
- package/apps/studio/test/knob-props-authored.test.ts +0 -87
- package/apps/studio/test/layers-synthetic-groups.test.ts +0 -96
- package/apps/studio/test/locator.test.ts +0 -214
- package/apps/studio/test/marquee-overlay.test.ts +0 -94
- package/apps/studio/test/media-commit-chain.test.ts +0 -210
- package/apps/studio/test/mode-toggle.test.tsx +0 -226
- package/apps/studio/test/motion-sample.test.ts +0 -35
- package/apps/studio/test/move-artboards-command.test.ts +0 -108
- package/apps/studio/test/participants-chrome.test.ts +0 -65
- package/apps/studio/test/paths.test.ts +0 -88
- package/apps/studio/test/pdf-print-boxes.test.ts +0 -359
- package/apps/studio/test/peer-selection-follows-camera.test.tsx +0 -131
- package/apps/studio/test/perf-harness.ts +0 -193
- package/apps/studio/test/phase-3.6-smoke.test.ts +0 -79
- package/apps/studio/test/photo-bg-remove-validation.test.ts +0 -71
- package/apps/studio/test/photo-canvas-bundle.test.ts +0 -79
- package/apps/studio/test/photo-edit-api.test.ts +0 -201
- package/apps/studio/test/photo-filters.test.ts +0 -149
- package/apps/studio/test/photo-pipeline.test.ts +0 -106
- package/apps/studio/test/photo-taxonomy.test.ts +0 -96
- package/apps/studio/test/print-marks.test.ts +0 -113
- package/apps/studio/test/print-units.test.ts +0 -173
- package/apps/studio/test/read-annotations.test.ts +0 -727
- package/apps/studio/test/read-only-gate.test.ts +0 -188
- package/apps/studio/test/read-only-role.test.ts +0 -95
- package/apps/studio/test/readiness.test.ts +0 -337
- package/apps/studio/test/remote-ahead-behind-cache.test.ts +0 -89
- package/apps/studio/test/reorder-api.test.ts +0 -210
- package/apps/studio/test/repo-concurrency.test.ts +0 -146
- package/apps/studio/test/repo-lock.test.ts +0 -350
- package/apps/studio/test/report-proxy.test.ts +0 -125
- package/apps/studio/test/ripple.test.ts +0 -197
- package/apps/studio/test/runtime-bundle-error-mapping.test.ts +0 -43
- package/apps/studio/test/runtime-bundle.test.ts +0 -69
- package/apps/studio/test/sanitize-annotation-svg.test.ts +0 -238
- package/apps/studio/test/scaffold-design.test.ts +0 -122
- package/apps/studio/test/server-lifecycle.test.ts +0 -38
- package/apps/studio/test/session-runtime-state.test.ts +0 -226
- package/apps/studio/test/session-scope.test.ts +0 -100
- package/apps/studio/test/shared-doc-cell-pairing.test.ts +0 -642
- package/apps/studio/test/shared-doc-convergence.test.ts +0 -265
- package/apps/studio/test/shared-doc-foundation.test.ts +0 -63
- package/apps/studio/test/shared-doc-migrate.test.ts +0 -369
- package/apps/studio/test/shared-doc-projection.test.ts +0 -238
- package/apps/studio/test/shell-importmap.test.ts +0 -56
- package/apps/studio/test/sizing-mode.test.ts +0 -102
- package/apps/studio/test/slash-commands.test.ts +0 -117
- package/apps/studio/test/snap-distance-pill.test.ts +0 -68
- package/apps/studio/test/spacing-handles.test.ts +0 -138
- package/apps/studio/test/specimen-select.test.ts +0 -88
- package/apps/studio/test/sync-agent.test.ts +0 -660
- package/apps/studio/test/sync-annotations-cold-start.test.ts +0 -302
- package/apps/studio/test/sync-asset-push.test.ts +0 -931
- package/apps/studio/test/sync-atomic-write.test.ts +0 -62
- package/apps/studio/test/sync-attach-incremental.test.ts +0 -527
- package/apps/studio/test/sync-autocommit.test.ts +0 -537
- package/apps/studio/test/sync-canvas-path.test.ts +0 -200
- package/apps/studio/test/sync-cell-write-nudge.test.ts +0 -346
- package/apps/studio/test/sync-codec.test.ts +0 -230
- package/apps/studio/test/sync-cold-seed-dedup.test.ts +0 -187
- package/apps/studio/test/sync-cold-start.test.ts +0 -304
- package/apps/studio/test/sync-connect-honesty.test.ts +0 -114
- package/apps/studio/test/sync-connection-state.test.ts +0 -337
- package/apps/studio/test/sync-ctl-channel.test.ts +0 -508
- package/apps/studio/test/sync-decide-file.test.ts +0 -420
- package/apps/studio/test/sync-doc-name.test.ts +0 -281
- package/apps/studio/test/sync-echo-guard.test.ts +0 -96
- package/apps/studio/test/sync-file-ledger.test.ts +0 -334
- package/apps/studio/test/sync-file-membership.test.ts +0 -347
- package/apps/studio/test/sync-file-plane.test.ts +0 -976
- package/apps/studio/test/sync-file-pull.test.ts +0 -333
- package/apps/studio/test/sync-fresh-link-parity.test.ts +0 -267
- package/apps/studio/test/sync-fs-mirror.test.ts +0 -181
- package/apps/studio/test/sync-hardening.test.ts +0 -586
- package/apps/studio/test/sync-hub-listing.test.ts +0 -46
- package/apps/studio/test/sync-hubs-config.test.ts +0 -133
- package/apps/studio/test/sync-incident-replay.test.ts +0 -211
- package/apps/studio/test/sync-journal.test.ts +0 -176
- package/apps/studio/test/sync-meta-codec.test.ts +0 -199
- package/apps/studio/test/sync-migrate-flat-fallback.test.ts +0 -98
- package/apps/studio/test/sync-move-retirement.test.ts +0 -231
- package/apps/studio/test/sync-panel-surface.test.ts +0 -153
- package/apps/studio/test/sync-path-pull.test.ts +0 -531
- package/apps/studio/test/sync-presentation.test.ts +0 -262
- package/apps/studio/test/sync-pull-budget.test.ts +0 -169
- package/apps/studio/test/sync-remote-docs.test.ts +0 -333
- package/apps/studio/test/sync-resync-routes.test.ts +0 -168
- package/apps/studio/test/sync-runtime.test.ts +0 -1675
- package/apps/studio/test/sync-seed-defers-to-hub.test.ts +0 -83
- package/apps/studio/test/sync-settings-routes.test.ts +0 -195
- package/apps/studio/test/sync-settings.test.ts +0 -151
- package/apps/studio/test/sync-status.test.ts +0 -261
- package/apps/studio/test/sync-supervisor.test.ts +0 -262
- package/apps/studio/test/sync-tombstone-apply.test.ts +0 -111
- package/apps/studio/test/sync-trash.test.ts +0 -132
- package/apps/studio/test/sync-two-peer-discovery.test.ts +0 -343
- package/apps/studio/test/sync-untrusted.test.ts +0 -103
- package/apps/studio/test/system-endpoint.test.ts +0 -144
- package/apps/studio/test/text-editability-stamp.test.ts +0 -131
- package/apps/studio/test/timeline-comp-target.test.ts +0 -139
- package/apps/studio/test/timeline-media-cache.test.ts +0 -41
- package/apps/studio/test/timeline-parse.test.ts +0 -184
- package/apps/studio/test/timeline-scale.test.ts +0 -65
- package/apps/studio/test/timeline-snap.test.ts +0 -85
- package/apps/studio/test/tool-palette-insert-anchor.test.ts +0 -86
- package/apps/studio/test/tool-palette.test.tsx +0 -55
- package/apps/studio/test/tour-overlay.test.tsx +0 -90
- package/apps/studio/test/trusted-request-host.test.ts +0 -66
- package/apps/studio/test/undo-sequence-byte-compare.test.ts +0 -211
- package/apps/studio/test/undo-stack.test.ts +0 -244
- package/apps/studio/test/use-agent-presence.test.tsx +0 -117
- package/apps/studio/test/use-annotation-resize.test.ts +0 -402
- package/apps/studio/test/use-annotation-selection.test.tsx +0 -77
- package/apps/studio/test/use-artboard-drag.test.ts +0 -324
- package/apps/studio/test/use-canvas-activity.test.tsx +0 -206
- package/apps/studio/test/use-collab.test.ts +0 -0
- package/apps/studio/test/use-cursor-modifiers.test.ts +0 -59
- package/apps/studio/test/use-keyboard-discipline.test.ts +0 -27
- package/apps/studio/test/use-selection-set.test.tsx +0 -166
- package/apps/studio/test/use-snap-guides.test.ts +0 -271
- package/apps/studio/test/use-tool-mode.test.tsx +0 -186
- package/apps/studio/test/use-undo-stack.test.tsx +0 -223
- package/apps/studio/test/video-asset.test.ts +0 -163
- package/apps/studio/test/video-comp-fixture.test.ts +0 -50
- package/apps/studio/test/video-comp.test.ts +0 -270
- package/apps/studio/test/video-encode-lib.test.ts +0 -63
- package/apps/studio/test/video-render-bridge.test.ts +0 -171
- package/apps/studio/test/whats-new.test.ts +0 -113
- package/apps/studio/test/whisper-setup.test.ts +0 -97
- package/apps/studio/test/workspace-containment.test.ts +0 -382
- package/apps/studio/test/workspace-signin.test.ts +0 -292
- package/apps/studio/test/ws-handshake.test.ts +0 -33
- package/cli/commands/cache.test.mjs +0 -166
- package/cli/commands/design-link.test.mjs +0 -419
- package/cli/commands/design.test.mjs +0 -240
- package/cli/commands/doctor.test.mjs +0 -237
- package/cli/commands/hub-workspace.test.mjs +0 -275
- package/cli/commands/hub.test.mjs +0 -225
- package/cli/commands/kg.test.mjs +0 -142
- package/cli/commands/preflight.test.mjs +0 -46
- package/cli/lib/argv.test.mjs +0 -46
- package/cli/lib/cache.test.mjs +0 -303
- package/cli/lib/cell-plan.test.mjs +0 -225
- package/cli/lib/config-lint.test.mjs +0 -117
- package/cli/lib/ddr-to-kgai.test.mjs +0 -118
- package/cli/lib/design-ownership.test.mjs +0 -329
- package/cli/lib/fetch-asset.test.mjs +0 -213
- package/cli/lib/figma-codegen-reachability.test.mjs +0 -104
- package/cli/lib/figma-import-controls.test.mjs +0 -70
- package/cli/lib/flow-design-integration.test.mjs +0 -165
- package/cli/lib/gitignore-block.test.mjs +0 -136
- package/cli/lib/gitignore-drift.test.mjs +0 -156
- package/cli/lib/hubs-config.test.mjs +0 -165
- package/cli/lib/pkg-root.test.mjs +0 -111
- package/cli/lib/plugin-cli-reachability.test.mjs +0 -57
- package/cli/lib/plugin-name-namespace.test.mjs +0 -71
- package/cli/lib/reconstruct-toolset.test.mjs +0 -194
- package/cli/lib/stack-detect.test.mjs +0 -121
- package/cli/lib/update-check.test.mjs +0 -32
- package/cli/lib/workspace-plan.test.mjs +0 -699
|
@@ -37,14 +37,28 @@ import { parseSync } from 'oxc-parser';
|
|
|
37
37
|
export class CanvasEditError extends Error {
|
|
38
38
|
readonly canvas: string;
|
|
39
39
|
readonly id: string;
|
|
40
|
-
|
|
40
|
+
/** The target no longer holds the value the caller expected (a peer changed it). */
|
|
41
|
+
readonly conflict: boolean;
|
|
42
|
+
constructor(message: string, info: { canvas: string; id: string; conflict?: boolean }) {
|
|
41
43
|
super(message);
|
|
42
44
|
this.name = 'CanvasEditError';
|
|
43
45
|
this.canvas = info.canvas;
|
|
44
46
|
this.id = info.id;
|
|
47
|
+
this.conflict = info.conflict === true;
|
|
45
48
|
}
|
|
46
49
|
}
|
|
47
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Expected-current-value guard for a single-attribute write (audit 2026-09-13
|
|
53
|
+
* P1 #5). `expected` is what the caller believes the source holds now, `next`
|
|
54
|
+
* is what it is about to write; `null` means "attribute / style key absent".
|
|
55
|
+
* Values are the RAW strings the edit routes take (not JSON-encoded).
|
|
56
|
+
*/
|
|
57
|
+
export interface AttributePrecondition {
|
|
58
|
+
expected: string | null;
|
|
59
|
+
next: string | null;
|
|
60
|
+
}
|
|
61
|
+
|
|
48
62
|
const PASCAL_CASE = /^[A-Z][A-Za-z0-9_]*$/;
|
|
49
63
|
// biome-ignore lint/suspicious/noExplicitAny: oxc-parser AST nodes are heterogeneous.
|
|
50
64
|
type AnyNode = any;
|
|
@@ -268,6 +282,12 @@ export interface EditResult {
|
|
|
268
282
|
* real write with delta 0 (RC1 rim-suppression finding).
|
|
269
283
|
*/
|
|
270
284
|
changed?: boolean;
|
|
285
|
+
/**
|
|
286
|
+
* What the target held BEFORE this write, read under the same lock. The
|
|
287
|
+
* value an undo must restore is what the write replaced — never what a
|
|
288
|
+
* client panel last believed was there (it can be a teammate's edit old).
|
|
289
|
+
*/
|
|
290
|
+
previous?: AttributeState;
|
|
271
291
|
}
|
|
272
292
|
|
|
273
293
|
/**
|
|
@@ -280,7 +300,8 @@ export async function editAttribute(
|
|
|
280
300
|
id: string,
|
|
281
301
|
attr: string,
|
|
282
302
|
value: string,
|
|
283
|
-
occurrence?: number
|
|
303
|
+
occurrence?: number,
|
|
304
|
+
precondition?: AttributePrecondition
|
|
284
305
|
): Promise<EditResult> {
|
|
285
306
|
return withLock(canvasAbsPath, async () => {
|
|
286
307
|
const file = Bun.file(canvasAbsPath);
|
|
@@ -291,13 +312,22 @@ export async function editAttribute(
|
|
|
291
312
|
});
|
|
292
313
|
}
|
|
293
314
|
const source = await file.text();
|
|
315
|
+
// Read under the same per-file lock as the write — no gap between the read
|
|
316
|
+
// that proves the precondition (and records what is replaced) and the rename.
|
|
317
|
+
const previous = readAttributeState(canvasAbsPath, source, id, attr, occurrence);
|
|
318
|
+
if (
|
|
319
|
+
precondition &&
|
|
320
|
+
checkPrecondition(previous, id, attr, canvasAbsPath, precondition) === 'already'
|
|
321
|
+
) {
|
|
322
|
+
return { source, delta: 0, changed: false, previous };
|
|
323
|
+
}
|
|
294
324
|
const next = applyEdit(canvasAbsPath, source, id, attr, value, occurrence);
|
|
295
|
-
if (next.source === source) return { source, delta: 0, changed: false };
|
|
325
|
+
if (next.source === source) return { source, delta: 0, changed: false, previous };
|
|
296
326
|
const tmp = `${canvasAbsPath}.tmp.${Math.random().toString(36).slice(2, 10)}`;
|
|
297
327
|
await Bun.write(tmp, next.source);
|
|
298
328
|
const { rename } = await import('node:fs/promises');
|
|
299
329
|
await rename(tmp, canvasAbsPath);
|
|
300
|
-
return { ...next, changed: true };
|
|
330
|
+
return { ...next, changed: true, previous };
|
|
301
331
|
});
|
|
302
332
|
}
|
|
303
333
|
|
|
@@ -313,7 +343,8 @@ export async function removeAttribute(
|
|
|
313
343
|
canvasAbsPath: string,
|
|
314
344
|
id: string,
|
|
315
345
|
attr: string,
|
|
316
|
-
occurrence?: number
|
|
346
|
+
occurrence?: number,
|
|
347
|
+
precondition?: AttributePrecondition
|
|
317
348
|
): Promise<EditResult> {
|
|
318
349
|
return withLock(canvasAbsPath, async () => {
|
|
319
350
|
const file = Bun.file(canvasAbsPath);
|
|
@@ -324,13 +355,124 @@ export async function removeAttribute(
|
|
|
324
355
|
});
|
|
325
356
|
}
|
|
326
357
|
const source = await file.text();
|
|
358
|
+
const previous = readAttributeState(canvasAbsPath, source, id, attr, occurrence);
|
|
359
|
+
if (
|
|
360
|
+
precondition &&
|
|
361
|
+
checkPrecondition(previous, id, attr, canvasAbsPath, precondition) === 'already'
|
|
362
|
+
) {
|
|
363
|
+
return { source, delta: 0, changed: false, previous };
|
|
364
|
+
}
|
|
327
365
|
const next = applyRemove(canvasAbsPath, source, id, attr, occurrence);
|
|
328
|
-
if (next.source === source) return { source, delta: 0, changed: false };
|
|
366
|
+
if (next.source === source) return { source, delta: 0, changed: false, previous };
|
|
329
367
|
const tmp = `${canvasAbsPath}.tmp.${Math.random().toString(36).slice(2, 10)}`;
|
|
330
368
|
await Bun.write(tmp, next.source);
|
|
331
369
|
const { rename } = await import('node:fs/promises');
|
|
332
370
|
await rename(tmp, canvasAbsPath);
|
|
333
|
-
return { ...next, changed: true };
|
|
371
|
+
return { ...next, changed: true, previous };
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/** What one plain attribute or inline style key currently holds in source. */
|
|
376
|
+
export type AttributeState =
|
|
377
|
+
| { kind: 'absent' }
|
|
378
|
+
| { kind: 'literal'; value: string }
|
|
379
|
+
/** Present, but not a literal this module can compare (an expression, a spread). */
|
|
380
|
+
| { kind: 'expression' };
|
|
381
|
+
|
|
382
|
+
function literalText(node: AnyNode): string | null {
|
|
383
|
+
if (!node) return null;
|
|
384
|
+
if (node.type === 'Literal' || node.type === 'StringLiteral' || node.type === 'NumericLiteral') {
|
|
385
|
+
return typeof node.value === 'string' || typeof node.value === 'number'
|
|
386
|
+
? String(node.value)
|
|
387
|
+
: null;
|
|
388
|
+
}
|
|
389
|
+
if (node.type === 'TemplateLiteral' && node.expressions?.length === 0) {
|
|
390
|
+
return node.quasis?.[0]?.value?.cooked ?? null;
|
|
391
|
+
}
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Read the current value of `attr` (`style.<prop>` or a plain attribute name)
|
|
397
|
+
* on the element with `data-cd-id` `id`, with the same occurrence routing as
|
|
398
|
+
* `applyEdit` / `applyRemove`. Pure — exposed for tests and preconditions.
|
|
399
|
+
*/
|
|
400
|
+
export function readAttributeState(
|
|
401
|
+
canvasAbsPath: string,
|
|
402
|
+
source: string,
|
|
403
|
+
id: string,
|
|
404
|
+
attr: string,
|
|
405
|
+
occurrence?: number
|
|
406
|
+
): AttributeState {
|
|
407
|
+
const parsed = parseSync(canvasAbsPath, source, { sourceType: 'module' });
|
|
408
|
+
if (parsed.errors && parsed.errors.length > 0) {
|
|
409
|
+
throw new CanvasEditError(
|
|
410
|
+
`oxc-parser failed on ${canvasAbsPath}: ${parsed.errors[0]?.message ?? 'unknown'}`,
|
|
411
|
+
{ canvas: canvasAbsPath, id }
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
if (typeof occurrence === 'number' && Number.isFinite(occurrence)) {
|
|
415
|
+
id = resolveUsageId(parsed.program, id, occurrence);
|
|
416
|
+
}
|
|
417
|
+
const hit = findOpening(parsed.program, id);
|
|
418
|
+
if (!hit) {
|
|
419
|
+
throw new CanvasEditError(`data-cd-id "${id}" not found in ${canvasAbsPath}`, {
|
|
420
|
+
canvas: canvasAbsPath,
|
|
421
|
+
id,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
if (attr.startsWith('style.')) {
|
|
425
|
+
const style = findAttribute(hit.opening, 'style');
|
|
426
|
+
if (!style) return { kind: 'absent' };
|
|
427
|
+
const obj = style.value?.type === 'JSXExpressionContainer' ? style.value.expression : null;
|
|
428
|
+
if (obj?.type !== 'ObjectExpression') return { kind: 'expression' };
|
|
429
|
+
const prop = attr.slice('style.'.length);
|
|
430
|
+
const propCamel = prop.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
431
|
+
for (const p of obj.properties as AnyNode[]) {
|
|
432
|
+
if (p?.type !== 'Property' && p?.type !== 'ObjectProperty') continue;
|
|
433
|
+
const k = p.key;
|
|
434
|
+
const kname =
|
|
435
|
+
k?.type === 'Identifier' ? k.name : k?.type === 'Literal' ? String(k.value) : null;
|
|
436
|
+
if (kname !== prop && kname !== propCamel) continue;
|
|
437
|
+
const text = literalText(p.value);
|
|
438
|
+
return text === null ? { kind: 'expression' } : { kind: 'literal', value: text };
|
|
439
|
+
}
|
|
440
|
+
// A spread may supply the key at runtime; "absent" would be a guess.
|
|
441
|
+
return (obj.properties as AnyNode[]).some((p) => p?.type === 'SpreadElement')
|
|
442
|
+
? { kind: 'expression' }
|
|
443
|
+
: { kind: 'absent' };
|
|
444
|
+
}
|
|
445
|
+
const found = findAttribute(hit.opening, attr);
|
|
446
|
+
if (!found) return { kind: 'absent' };
|
|
447
|
+
if (found.value == null) return { kind: 'literal', value: '' };
|
|
448
|
+
const direct = literalText(found.value);
|
|
449
|
+
if (direct !== null) return { kind: 'literal', value: direct };
|
|
450
|
+
const inner =
|
|
451
|
+
found.value.type === 'JSXExpressionContainer' ? literalText(found.value.expression) : null;
|
|
452
|
+
return inner === null ? { kind: 'expression' } : { kind: 'literal', value: inner };
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* `'apply'` when the source still holds `expected`; `'already'` when it holds
|
|
457
|
+
* `next` (a retried or already-applied write — idempotent success). Anything
|
|
458
|
+
* else is a peer's newer value: refuse rather than overwrite it.
|
|
459
|
+
*/
|
|
460
|
+
function checkPrecondition(
|
|
461
|
+
state: AttributeState,
|
|
462
|
+
id: string,
|
|
463
|
+
attr: string,
|
|
464
|
+
canvasAbsPath: string,
|
|
465
|
+
pre: AttributePrecondition
|
|
466
|
+
): 'apply' | 'already' {
|
|
467
|
+
const holds = (value: string | null) =>
|
|
468
|
+
value === null ? state.kind === 'absent' : state.kind === 'literal' && state.value === value;
|
|
469
|
+
if (holds(pre.next)) return 'already';
|
|
470
|
+
if (holds(pre.expected)) return 'apply';
|
|
471
|
+
const name = attr.startsWith('style.') ? attr.slice('style.'.length) : attr;
|
|
472
|
+
throw new CanvasEditError(`${name} was changed by someone else — kept their value`, {
|
|
473
|
+
canvas: canvasAbsPath,
|
|
474
|
+
id,
|
|
475
|
+
conflict: true,
|
|
334
476
|
});
|
|
335
477
|
}
|
|
336
478
|
|
|
@@ -3930,6 +4072,86 @@ export function applySetArtboardKind(
|
|
|
3930
4072
|
return { source: out };
|
|
3931
4073
|
}
|
|
3932
4074
|
|
|
4075
|
+
/** Longest artboard name the label edit accepts (the chrome truncates long ones). */
|
|
4076
|
+
export const MAX_ARTBOARD_LABEL = 80;
|
|
4077
|
+
|
|
4078
|
+
/**
|
|
4079
|
+
* Rename an artboard — write its DCArtboard `label` string prop (plan T25/L08,
|
|
4080
|
+
* the double-click-the-name gesture). Addressed by the `id` prop like every
|
|
4081
|
+
* other artboard verb; the label is a JSX string attribute, so `editStringAttr`
|
|
4082
|
+
* escapes it. Pure; reparse-gated.
|
|
4083
|
+
*/
|
|
4084
|
+
export function applySetArtboardLabel(
|
|
4085
|
+
canvasAbsPath: string,
|
|
4086
|
+
source: string,
|
|
4087
|
+
artboardId: string,
|
|
4088
|
+
label: string
|
|
4089
|
+
): { source: string } {
|
|
4090
|
+
// Control characters never reach a JSX attribute.
|
|
4091
|
+
const clean = [...label]
|
|
4092
|
+
.filter((c) => c.charCodeAt(0) >= 0x20 && c.charCodeAt(0) !== 0x7f)
|
|
4093
|
+
.join('')
|
|
4094
|
+
.trim();
|
|
4095
|
+
if (!clean || clean.length > MAX_ARTBOARD_LABEL) {
|
|
4096
|
+
throw new CanvasEditError(`artboard name must be 1–${MAX_ARTBOARD_LABEL} characters`, {
|
|
4097
|
+
canvas: canvasAbsPath,
|
|
4098
|
+
id: artboardId,
|
|
4099
|
+
});
|
|
4100
|
+
}
|
|
4101
|
+
const parsed = parseSync(canvasAbsPath, source, { sourceType: 'module' });
|
|
4102
|
+
if (parsed.errors && parsed.errors.length > 0) {
|
|
4103
|
+
throw new CanvasEditError(
|
|
4104
|
+
`oxc-parser failed on ${canvasAbsPath}: ${parsed.errors[0]?.message ?? 'unknown'}`,
|
|
4105
|
+
{ canvas: canvasAbsPath, id: artboardId }
|
|
4106
|
+
);
|
|
4107
|
+
}
|
|
4108
|
+
const target = collectJsxByTag(parsed.program, 'DCArtboard').find(
|
|
4109
|
+
(a) => getStringAttr(a.openingElement, 'id') === artboardId
|
|
4110
|
+
);
|
|
4111
|
+
if (!target) {
|
|
4112
|
+
throw new CanvasEditError(`<DCArtboard id="${artboardId}"> not found in ${canvasAbsPath}`, {
|
|
4113
|
+
canvas: canvasAbsPath,
|
|
4114
|
+
id: artboardId,
|
|
4115
|
+
});
|
|
4116
|
+
}
|
|
4117
|
+
const s = new MagicString(source);
|
|
4118
|
+
editStringAttr(s, target.openingElement, 'label', clean, canvasAbsPath, artboardId);
|
|
4119
|
+
const out = s.toString();
|
|
4120
|
+
const check = parseSync(canvasAbsPath, out, { sourceType: 'module' });
|
|
4121
|
+
if (check.errors && check.errors.length > 0) {
|
|
4122
|
+
throw new CanvasEditError(
|
|
4123
|
+
`artboard rename produced invalid source (${check.errors[0]?.message ?? 'parse error'})`,
|
|
4124
|
+
{ canvas: canvasAbsPath, id: artboardId }
|
|
4125
|
+
);
|
|
4126
|
+
}
|
|
4127
|
+
return { source: out };
|
|
4128
|
+
}
|
|
4129
|
+
|
|
4130
|
+
/** Rename an artboard on disk (atomic write + cross-process lock). */
|
|
4131
|
+
export async function setArtboardLabel(
|
|
4132
|
+
canvasAbsPath: string,
|
|
4133
|
+
artboardId: string,
|
|
4134
|
+
label: string
|
|
4135
|
+
): Promise<{ source: string }> {
|
|
4136
|
+
return withLock(canvasAbsPath, async () => {
|
|
4137
|
+
const file = Bun.file(canvasAbsPath);
|
|
4138
|
+
if (!(await file.exists())) {
|
|
4139
|
+
throw new CanvasEditError(`Canvas not found: ${canvasAbsPath}`, {
|
|
4140
|
+
canvas: canvasAbsPath,
|
|
4141
|
+
id: artboardId,
|
|
4142
|
+
});
|
|
4143
|
+
}
|
|
4144
|
+
const source = await file.text();
|
|
4145
|
+
const next = applySetArtboardLabel(canvasAbsPath, source, artboardId, label);
|
|
4146
|
+
if (next.source === source) return next;
|
|
4147
|
+
const tmp = `${canvasAbsPath}.tmp.${Math.random().toString(36).slice(2, 10)}`;
|
|
4148
|
+
await Bun.write(tmp, next.source);
|
|
4149
|
+
const { rename } = await import('node:fs/promises');
|
|
4150
|
+
await rename(tmp, canvasAbsPath);
|
|
4151
|
+
return next;
|
|
4152
|
+
});
|
|
4153
|
+
}
|
|
4154
|
+
|
|
3933
4155
|
/** Set an artboard's `kind` on disk (atomic write + cross-process lock). */
|
|
3934
4156
|
export async function setArtboardKind(
|
|
3935
4157
|
canvasAbsPath: string,
|
|
@@ -5354,3 +5576,165 @@ if (import.meta.main) {
|
|
|
5354
5576
|
process.exit(2);
|
|
5355
5577
|
}
|
|
5356
5578
|
}
|
|
5579
|
+
|
|
5580
|
+
// ---------------------------------------------------------------------------
|
|
5581
|
+
// Position-independent element identity — plan T23/T24 (DDR-241).
|
|
5582
|
+
//
|
|
5583
|
+
// `data-cd-id` is positional (component + pre-order index), so a sibling a
|
|
5584
|
+
// teammate inserted earlier in the same component renumbers everything after
|
|
5585
|
+
// it. An edit made through the UI is re-applied onto the project's newer
|
|
5586
|
+
// version when its own proposal lost a race (sync/source-ops.ts); before it
|
|
5587
|
+
// touches anything there, it has to find THE SAME element — by what the
|
|
5588
|
+
// element is, not where it was.
|
|
5589
|
+
//
|
|
5590
|
+
// A print is the enclosing component, the tag, the chain of ancestor tags and
|
|
5591
|
+
// the element's literal attributes (minus the one being edited, minus the
|
|
5592
|
+
// pipeline's own `data-cd-*`). It identifies an element when exactly one
|
|
5593
|
+
// element in the newer source carries it; anything else is ambiguous, and an
|
|
5594
|
+
// ambiguous edit is never guessed at.
|
|
5595
|
+
|
|
5596
|
+
export interface ElementPrint {
|
|
5597
|
+
component: string;
|
|
5598
|
+
tag: string;
|
|
5599
|
+
chain: string[];
|
|
5600
|
+
attrs: string;
|
|
5601
|
+
/** The element's own literal text (bounded) — absent for a text edit, whose text is what changes. */
|
|
5602
|
+
text?: string;
|
|
5603
|
+
}
|
|
5604
|
+
|
|
5605
|
+
function ownText(node: AnyNode): string {
|
|
5606
|
+
let t = '';
|
|
5607
|
+
for (const c of Array.isArray(node?.children) ? node.children : []) {
|
|
5608
|
+
if (c?.type === 'JSXText') t += String(c.value);
|
|
5609
|
+
}
|
|
5610
|
+
return t.replace(/\s+/g, ' ').trim().slice(0, 80);
|
|
5611
|
+
}
|
|
5612
|
+
|
|
5613
|
+
function literalAttrs(opening: AnyNode, ignore: string | null): string {
|
|
5614
|
+
const out: string[] = [];
|
|
5615
|
+
for (const a of Array.isArray(opening?.attributes) ? opening.attributes : []) {
|
|
5616
|
+
if (a?.type !== 'JSXAttribute' || a.name?.type !== 'JSXIdentifier') continue;
|
|
5617
|
+
const name = String(a.name.name);
|
|
5618
|
+
if (name.startsWith('data-cd-') || name === 'style' || name === ignore || name === 'key')
|
|
5619
|
+
continue;
|
|
5620
|
+
const v = a.value;
|
|
5621
|
+
const lit =
|
|
5622
|
+
v?.type === 'Literal' || v?.type === 'StringLiteral'
|
|
5623
|
+
? String(v.value)
|
|
5624
|
+
: v?.type === 'JSXExpressionContainer' &&
|
|
5625
|
+
(v.expression?.type === 'Literal' || v.expression?.type === 'StringLiteral')
|
|
5626
|
+
? String(v.expression.value)
|
|
5627
|
+
: v === null || v === undefined
|
|
5628
|
+
? 'true'
|
|
5629
|
+
: '{expr}';
|
|
5630
|
+
out.push(`${name}=${lit}`);
|
|
5631
|
+
}
|
|
5632
|
+
return out.sort().join('|');
|
|
5633
|
+
}
|
|
5634
|
+
|
|
5635
|
+
function printAll(
|
|
5636
|
+
canvasAbsPath: string,
|
|
5637
|
+
source: string,
|
|
5638
|
+
ignoreAttr: string | null,
|
|
5639
|
+
withText = true
|
|
5640
|
+
): Array<{ id: string; print: ElementPrint }> {
|
|
5641
|
+
const parsed = parseSync(canvasAbsPath, source, { sourceType: 'module' });
|
|
5642
|
+
if (parsed.errors && parsed.errors.length > 0) return [];
|
|
5643
|
+
const frames: Array<{ componentName: string; jsxIndex: number }> = [
|
|
5644
|
+
{ componentName: '', jsxIndex: 0 },
|
|
5645
|
+
];
|
|
5646
|
+
const chain: string[] = [];
|
|
5647
|
+
const out: Array<{ id: string; print: ElementPrint }> = [];
|
|
5648
|
+
function visit(node: AnyNode): void {
|
|
5649
|
+
if (!node || typeof node !== 'object') return;
|
|
5650
|
+
if (Array.isArray(node)) {
|
|
5651
|
+
for (const c of node) visit(c);
|
|
5652
|
+
return;
|
|
5653
|
+
}
|
|
5654
|
+
if (typeof node.type !== 'string') return;
|
|
5655
|
+
const comp = componentNameOf(node);
|
|
5656
|
+
if (comp !== null) frames.push({ componentName: comp, jsxIndex: 0 });
|
|
5657
|
+
if (node.type === 'JSXElement') {
|
|
5658
|
+
const frame = frames[frames.length - 1] as { componentName: string; jsxIndex: number };
|
|
5659
|
+
const idx = frame.jsxIndex;
|
|
5660
|
+
frame.jsxIndex += 1;
|
|
5661
|
+
const tag = jsxTagName(node) ?? '?';
|
|
5662
|
+
out.push({
|
|
5663
|
+
id: authoredCdId(node.openingElement) ?? computeId(frame.componentName, idx),
|
|
5664
|
+
print: {
|
|
5665
|
+
component: frame.componentName,
|
|
5666
|
+
tag,
|
|
5667
|
+
chain: [...chain],
|
|
5668
|
+
attrs: literalAttrs(node.openingElement, ignoreAttr),
|
|
5669
|
+
...(withText ? { text: ownText(node) } : {}),
|
|
5670
|
+
},
|
|
5671
|
+
});
|
|
5672
|
+
chain.push(tag);
|
|
5673
|
+
if (node.openingElement) visit(node.openingElement.attributes);
|
|
5674
|
+
visit(node.children);
|
|
5675
|
+
chain.pop();
|
|
5676
|
+
if (comp !== null) frames.pop();
|
|
5677
|
+
return;
|
|
5678
|
+
}
|
|
5679
|
+
for (const k of Object.keys(node)) {
|
|
5680
|
+
if (k === 'loc' || k === 'range' || k === 'start' || k === 'end' || k === 'type') continue;
|
|
5681
|
+
visit(node[k]);
|
|
5682
|
+
}
|
|
5683
|
+
if (comp !== null) frames.pop();
|
|
5684
|
+
}
|
|
5685
|
+
visit(parsed.program);
|
|
5686
|
+
return out;
|
|
5687
|
+
}
|
|
5688
|
+
|
|
5689
|
+
const samePrint = (a: ElementPrint, b: ElementPrint) =>
|
|
5690
|
+
a.component === b.component &&
|
|
5691
|
+
a.tag === b.tag &&
|
|
5692
|
+
a.attrs === b.attrs &&
|
|
5693
|
+
(a.text ?? null) === (b.text ?? null) &&
|
|
5694
|
+
a.chain.length === b.chain.length &&
|
|
5695
|
+
a.chain.every((t, i) => t === b.chain[i]);
|
|
5696
|
+
|
|
5697
|
+
/** The print of the element `id` in `source`, or null. */
|
|
5698
|
+
export function elementPrint(
|
|
5699
|
+
canvasAbsPath: string,
|
|
5700
|
+
source: string,
|
|
5701
|
+
id: string,
|
|
5702
|
+
ignoreAttr: string | null = null,
|
|
5703
|
+
withText = true
|
|
5704
|
+
): ElementPrint | null {
|
|
5705
|
+
return (
|
|
5706
|
+
printAll(canvasAbsPath, source, ignoreAttr, withText).find((e) => e.id === id)?.print ?? null
|
|
5707
|
+
);
|
|
5708
|
+
}
|
|
5709
|
+
|
|
5710
|
+
/**
|
|
5711
|
+
* The id the element printed `print` has in `source` — `hint` first (the
|
|
5712
|
+
* common case: nothing moved), else the ONE element carrying the print. Null
|
|
5713
|
+
* when it is gone or ambiguous.
|
|
5714
|
+
*/
|
|
5715
|
+
export function relocateElement(
|
|
5716
|
+
canvasAbsPath: string,
|
|
5717
|
+
source: string,
|
|
5718
|
+
hint: string,
|
|
5719
|
+
print: ElementPrint,
|
|
5720
|
+
ignoreAttr: string | null = null
|
|
5721
|
+
): string | null {
|
|
5722
|
+
const all = printAll(canvasAbsPath, source, ignoreAttr, print.text !== undefined);
|
|
5723
|
+
const atHint = all.find((e) => e.id === hint);
|
|
5724
|
+
if (atHint && samePrint(atHint.print, print)) return hint;
|
|
5725
|
+
const matches = all.filter((e) => samePrint(e.print, print));
|
|
5726
|
+
return matches.length === 1 ? (matches[0] as { id: string }).id : null;
|
|
5727
|
+
}
|
|
5728
|
+
|
|
5729
|
+
/** T23 — how many elements of `source` carry a print no other element shares. */
|
|
5730
|
+
export function printUniqueness(
|
|
5731
|
+
canvasAbsPath: string,
|
|
5732
|
+
source: string
|
|
5733
|
+
): { elements: number; unique: number } {
|
|
5734
|
+
const all = printAll(canvasAbsPath, source, null);
|
|
5735
|
+
const key = (p: ElementPrint) =>
|
|
5736
|
+
`${p.component}|${p.tag}|${p.chain.join('>')}|${p.attrs}|${p.text ?? ''}`;
|
|
5737
|
+
const counts = new Map<string, number>();
|
|
5738
|
+
for (const e of all) counts.set(key(e.print), (counts.get(key(e.print)) ?? 0) + 1);
|
|
5739
|
+
return { elements: all.length, unique: all.filter((e) => counts.get(key(e.print)) === 1).length };
|
|
5740
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Relative-import rewriting for a canvas that CHANGES FOLDER (issue #114).
|
|
2
|
+
//
|
|
3
|
+
// THE BUG THIS MODULE EXISTS TO FIX. `moveCanvas` / `moveFolder` relocated a
|
|
4
|
+
// canvas with a bare `rename()` — bytes moved, specifiers didn't. A canvas
|
|
5
|
+
// dragged from `ui/` into `ui/print/` kept
|
|
6
|
+
//
|
|
7
|
+
// import { … } from "../system/alligators/preview/_kit";
|
|
8
|
+
// import "../system/alligators/preview/_layout.css";
|
|
9
|
+
//
|
|
10
|
+
// which is only correct one level up, so the canvas 500s on the next build with
|
|
11
|
+
// `Could not resolve`. Nothing anywhere in the tree rewrote imports; the
|
|
12
|
+
// capability simply did not exist. It reads like a sync bug from the outside
|
|
13
|
+
// (the broken file shows up on every peer), which is how it got reported
|
|
14
|
+
// alongside one — but it is entirely local to the move.
|
|
15
|
+
//
|
|
16
|
+
// Deliberately regex-over-known-vocabulary rather than a parser: it matches the
|
|
17
|
+
// house style of the other static readers (`_canvas-rects-static.mjs`), it
|
|
18
|
+
// cannot fail closed on a canvas whose syntax a parser would reject, and the
|
|
19
|
+
// vocabulary is small and fixed. Only `./`- and `../`-prefixed specifiers are
|
|
20
|
+
// touched — a bare or aliased specifier (`react`, `@maude/canvas-lib`) is
|
|
21
|
+
// resolved by the bundler, not by depth, and rewriting one would break it.
|
|
22
|
+
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Every shape that can carry a module specifier in a canvas body, as
|
|
27
|
+
* [pattern, index of the capture group holding the quoted specifier].
|
|
28
|
+
*
|
|
29
|
+
* from "…" — `import x from`, `export … from`
|
|
30
|
+
* import "…" — side-effect import (this is the one that carries `_layout.css`)
|
|
31
|
+
* import("…") — dynamic import
|
|
32
|
+
*
|
|
33
|
+
* `require("…")` is deliberately absent: canvases are ESM (Bun.build), and a
|
|
34
|
+
* `require` in one is not something this should quietly rewrite.
|
|
35
|
+
*/
|
|
36
|
+
const SPECIFIER_PATTERNS: readonly RegExp[] = [
|
|
37
|
+
/(\bfrom\s*)(["'])([^"']+)\2/g,
|
|
38
|
+
/(\bimport\s*)(["'])([^"']+)\2/g,
|
|
39
|
+
/(\bimport\s*\(\s*)(["'])([^"']+)\2/g,
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
/** Is this a path-relative specifier (the only kind whose meaning depends on
|
|
43
|
+
* which folder the importing file sits in)? */
|
|
44
|
+
export function isRelativeSpecifier(spec: string): boolean {
|
|
45
|
+
return spec.startsWith('./') || spec.startsWith('../');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Re-root one relative specifier from `fromDir` to `toDir`.
|
|
50
|
+
*
|
|
51
|
+
* Both dirs are POSIX, design-root-relative (`''` = the design root itself).
|
|
52
|
+
* Returns the specifier unchanged when it isn't relative.
|
|
53
|
+
*/
|
|
54
|
+
export function rerootSpecifier(spec: string, fromDir: string, toDir: string): string {
|
|
55
|
+
if (!isRelativeSpecifier(spec)) return spec;
|
|
56
|
+
// What the specifier pointed AT, as a design-root-relative path. `join` +
|
|
57
|
+
// `normalize` collapse the `..` segments; a target above the design root stays
|
|
58
|
+
// expressed with leading `..` and re-roots just as correctly.
|
|
59
|
+
const target = path.posix.normalize(path.posix.join(fromDir, spec));
|
|
60
|
+
let next = path.posix.relative(toDir, target);
|
|
61
|
+
// `relative` drops the leading `./` that makes a same-dir specifier relative
|
|
62
|
+
// rather than bare — without it `sibling.css` would resolve as a PACKAGE.
|
|
63
|
+
if (!next.startsWith('.')) next = `./${next}`;
|
|
64
|
+
// A trailing slash in the source (`from "../lib/"`) is meaningful to a
|
|
65
|
+
// resolver; `normalize`/`relative` drop it, so put it back.
|
|
66
|
+
if (spec.endsWith('/') && !next.endsWith('/')) next = `${next}/`;
|
|
67
|
+
return next;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Rewrite every relative import in `source` for a file that moved from
|
|
72
|
+
* `fromDir` to `toDir` (both POSIX, design-root-relative).
|
|
73
|
+
*
|
|
74
|
+
* Returns the source unchanged when the two dirs are the same or nothing
|
|
75
|
+
* relative was found, so a caller can cheaply skip the write.
|
|
76
|
+
*/
|
|
77
|
+
export function rewriteRelativeImports(source: string, fromDir: string, toDir: string): string {
|
|
78
|
+
const from = path.posix.normalize(fromDir === '' ? '.' : fromDir);
|
|
79
|
+
const to = path.posix.normalize(toDir === '' ? '.' : toDir);
|
|
80
|
+
if (from === to) return source;
|
|
81
|
+
let out = source;
|
|
82
|
+
for (const pattern of SPECIFIER_PATTERNS) {
|
|
83
|
+
out = out.replace(pattern, (whole, lead: string, quote: string, spec: string) => {
|
|
84
|
+
if (!isRelativeSpecifier(spec)) return whole;
|
|
85
|
+
return `${lead}${quote}${rerootSpecifier(spec, from, to)}${quote}`;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
@@ -475,6 +475,17 @@ button.dc-artboard-label {
|
|
|
475
475
|
width: 100%;
|
|
476
476
|
}
|
|
477
477
|
button.dc-artboard-label:focus-visible { outline: 2px solid var(--maude-hud-accent, #d63b1f); outline-offset: -2px; }
|
|
478
|
+
input.dc-artboard-rename {
|
|
479
|
+
appearance: none;
|
|
480
|
+
border-width: 0 0 1px 0;
|
|
481
|
+
font: inherit;
|
|
482
|
+
display: block;
|
|
483
|
+
width: 100%;
|
|
484
|
+
box-sizing: border-box;
|
|
485
|
+
text-transform: none;
|
|
486
|
+
outline: 2px solid var(--maude-hud-accent, #d63b1f);
|
|
487
|
+
outline-offset: -2px;
|
|
488
|
+
}
|
|
478
489
|
/* DDR-148 — video-artboard badge, overlaid top-right of the header. Opens
|
|
479
490
|
the timeline panel (same postMessage the context-menu entry sends). */
|
|
480
491
|
.dc-canvas .dc-artboard-video-badge {
|
|
@@ -2447,6 +2458,25 @@ export function DCArtboard({
|
|
|
2447
2458
|
// edited), so it never ratchets up from a previously-larger measured value
|
|
2448
2459
|
// the way feeding `rect.h` back as the floor would.
|
|
2449
2460
|
const heightFloor = typeof height === 'number' ? height : VP_GRID.h;
|
|
2461
|
+
// Plan T25/L08 — rename in place: double-click the name, Enter commits (the
|
|
2462
|
+
// shell writes the `label` prop), Escape or an unchanged name cancels.
|
|
2463
|
+
const [renaming, setRenaming] = useState(false);
|
|
2464
|
+
const renameDoneRef = useRef(false);
|
|
2465
|
+
const commitRename = (value: string) => {
|
|
2466
|
+
if (renameDoneRef.current) return;
|
|
2467
|
+
renameDoneRef.current = true;
|
|
2468
|
+
setRenaming(false);
|
|
2469
|
+
const next = value.trim();
|
|
2470
|
+
if (!next || next === label) return;
|
|
2471
|
+
try {
|
|
2472
|
+
window.parent.postMessage(
|
|
2473
|
+
{ dgn: 'rename-artboard-request', artboardId: id, label: next },
|
|
2474
|
+
'*'
|
|
2475
|
+
);
|
|
2476
|
+
} catch {
|
|
2477
|
+
/* detached / cross-origin */
|
|
2478
|
+
}
|
|
2479
|
+
};
|
|
2450
2480
|
const articleRef = useRef<HTMLElement | null>(null);
|
|
2451
2481
|
const measured = useArtboardBounds(articleRef as RefObject<HTMLElement | null>);
|
|
2452
2482
|
useEffect(() => {
|
|
@@ -2590,24 +2620,58 @@ export function DCArtboard({
|
|
|
2590
2620
|
}}
|
|
2591
2621
|
{...handleProps}
|
|
2592
2622
|
>
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2623
|
+
{renaming ? (
|
|
2624
|
+
<input
|
|
2625
|
+
className="dc-artboard-label sku dc-artboard-rename"
|
|
2626
|
+
data-testid={`artboard-rename-${id}`}
|
|
2627
|
+
aria-label="Artboard name"
|
|
2628
|
+
defaultValue={label}
|
|
2629
|
+
maxLength={80}
|
|
2630
|
+
// biome-ignore lint/a11y/noAutofocus: the rename field opens on the designer's own double-click
|
|
2631
|
+
autoFocus
|
|
2632
|
+
onFocus={(e) => e.currentTarget.select()}
|
|
2633
|
+
// The label is the drag handle; typing and clicking in the field
|
|
2634
|
+
// must not start a move or reach the canvas shortcuts.
|
|
2635
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
2636
|
+
onMouseDown={(e) => e.stopPropagation()}
|
|
2637
|
+
onKeyDown={(e) => {
|
|
2638
|
+
e.stopPropagation();
|
|
2639
|
+
if (e.key === 'Enter') commitRename(e.currentTarget.value);
|
|
2640
|
+
else if (e.key === 'Escape') {
|
|
2641
|
+
renameDoneRef.current = true;
|
|
2642
|
+
setRenaming(false);
|
|
2643
|
+
}
|
|
2644
|
+
}}
|
|
2645
|
+
onBlur={(e) => commitRename(e.currentTarget.value)}
|
|
2646
|
+
/>
|
|
2647
|
+
) : (
|
|
2648
|
+
<button
|
|
2649
|
+
type="button"
|
|
2650
|
+
className="dc-artboard-label sku"
|
|
2651
|
+
onDoubleClick={(e) => {
|
|
2652
|
+
if (isReadOnlyCanvas()) return;
|
|
2653
|
+
e.stopPropagation();
|
|
2654
|
+
renameDoneRef.current = false;
|
|
2655
|
+
setRenaming(true);
|
|
2656
|
+
}}
|
|
2657
|
+
// a11y-auditor (T3 review) — the kind chip is aria-hidden (decorative,
|
|
2658
|
+
// redundant with the Inspector's Kind picker), so a non-digital kind
|
|
2659
|
+
// must still reach the artboard's own accessible name or it's
|
|
2660
|
+
// invisible to AT users entirely.
|
|
2661
|
+
aria-label={
|
|
2662
|
+
resolvedKind !== 'digital'
|
|
2663
|
+
? `Artboard ${label}, ${resolvedKind}`
|
|
2664
|
+
: `Artboard ${label}`
|
|
2665
|
+
}
|
|
2666
|
+
>
|
|
2667
|
+
{resolvedKind !== 'digital' ? (
|
|
2668
|
+
<span className="dc-artboard-kind-chip" aria-hidden="true">
|
|
2669
|
+
<ArtboardKindIcon kind={resolvedKind} />
|
|
2670
|
+
</span>
|
|
2671
|
+
) : null}
|
|
2672
|
+
{label}
|
|
2673
|
+
</button>
|
|
2674
|
+
)}
|
|
2611
2675
|
{hasVideo ? (
|
|
2612
2676
|
<button
|
|
2613
2677
|
type="button"
|