@agent-native/core 0.169.1 → 0.170.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/agent-native.eject.json +4 -1
- package/bin/agent-native.js +23 -5
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +1 -1
- package/corpus/templates/analytics/app/i18n-data.ts +28 -0
- package/corpus/templates/analytics/app/pages/Agents.tsx +25 -1
- package/corpus/templates/analytics/app/routes/$.tsx +10 -0
- package/corpus/templates/analytics/app/routes.ts +3 -1
- package/corpus/templates/analytics/changelog/2026-08-22-fixed-the-agent-native-templates-first-party-dashboard-panel.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-22-org-admin-panels-no-longer-report-a-database-error-as-a-per.md +9 -0
- package/corpus/templates/analytics/changelog/2026-08-22-slack-requests-report-invalid-inputs.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-22-unknown-analytics-links-now-return-a-real-not-found-status.md +6 -0
- package/corpus/templates/analytics/docs/schemas/first-party-analytics.md +2 -1
- package/corpus/templates/analytics/docs/schemas/tracked-events.md +2 -0
- package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +29 -7
- package/corpus/templates/analytics/server/handlers/slack.ts +59 -5
- package/corpus/templates/analytics/server/lib/db-admin-connections.ts +11 -6
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +132 -2
- package/corpus/templates/analytics/server/lib/slack.ts +4 -2
- package/corpus/templates/analytics/vitest.config.ts +6 -1
- package/corpus/templates/assets/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/assets/app/routes/library.tsx +5 -3
- package/corpus/templates/assets/app/routes.ts +3 -1
- package/corpus/templates/assets/changelog/2026-08-22-library-page-loads-again-instead-of-crashing-on-open.md +6 -0
- package/corpus/templates/assets/vitest.config.ts +6 -1
- package/corpus/templates/brain/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/brain/app/components/ui/pointer-lock.ts +1 -0
- package/corpus/templates/brain/app/routes/sources.tsx +8 -3
- package/corpus/templates/brain/changelog/2026-08-22-tuning-or-adding-a-source-no-longer-leaves-the-sources-page.md +6 -0
- package/corpus/templates/brain/vitest.config.ts +1 -0
- package/corpus/templates/calendar/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/calendar/actions/list-booking-links.ts +83 -4
- package/corpus/templates/calendar/actions/update-booking-link.ts +4 -3
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +11 -3
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +1 -1
- package/corpus/templates/calendar/app/components/calendar/GoogleConnectBanner.tsx +103 -44
- package/corpus/templates/calendar/app/components/calendar/OutOfOfficeEvent.tsx +28 -24
- package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +9 -2
- package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +47 -58
- package/corpus/templates/calendar/app/hooks/use-booking-links.ts +5 -1
- package/corpus/templates/calendar/app/lib/event-layout.ts +52 -26
- package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +126 -98
- package/corpus/templates/calendar/app/pages/CalendarView.tsx +1 -0
- package/corpus/templates/calendar/app/pages/Settings.tsx +88 -81
- package/corpus/templates/calendar/app/routes.ts +3 -1
- package/corpus/templates/calendar/changelog/2026-08-22-booking-link-visibility-respects-shared-access.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-22-clicking-an-out-of-office-event-s-marker-no-longer-creates-a.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-22-event-detail-views-now-show-your-calendar-timezone.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-22-google-calendar-can-now-connect-with-the-shared-google-oauth.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-22-overlapping-events-now-show-each-others-boundaries.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-22-public-booking-pages-now-offer-time-slots-from-the-host-s-we.md +6 -0
- package/corpus/templates/calendar/server/handlers/bookings.ts +5 -7
- package/corpus/templates/calendar/server/handlers/google-auth.ts +10 -2
- package/corpus/templates/calendar/server/lib/google-calendar.ts +139 -14
- package/corpus/templates/calendar/server/onboarding.ts +85 -0
- package/corpus/templates/calendar/server/plugins/agent-chat.ts +2 -0
- package/corpus/templates/calendar/shared/api.ts +9 -1
- package/corpus/templates/chat/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/chat/app/routes.ts +3 -1
- package/corpus/templates/chat/vitest.config.ts +8 -0
- package/corpus/templates/clips/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/clips/actions/get-meeting.ts +9 -4
- package/corpus/templates/clips/actions/list-recordings.ts +16 -3
- package/corpus/templates/clips/actions/search-meetings.ts +1 -0
- package/corpus/templates/clips/actions/view-screen.ts +8 -1
- package/corpus/templates/clips/app/components/meetings/meeting-history-row.tsx +32 -1
- package/corpus/templates/clips/app/components/player/delete-recording-menu.tsx +1 -1
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +29 -1
- package/corpus/templates/clips/app/i18n/en-US.ts +5 -0
- package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +33 -4
- package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +1 -0
- package/corpus/templates/clips/app/routes/access-request.approve.tsx +16 -9
- package/corpus/templates/clips/app/routes/embed.$shareId.tsx +17 -7
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +17 -7
- package/corpus/templates/clips/changelog/2026-08-21-camera-bubble-black-circle.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-22-screen-recordings-respect-the-microphone-toggle.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-22-the-recordings-library-no-longer-fails-right-after-server-start.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-23-the-agent-can-see-your-library-right-after-server-start.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/background.ts +1 -2
- package/corpus/templates/clips/chrome-extension/src/native-transcription.ts +39 -7
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +21 -25
- package/corpus/templates/clips/desktop/src/app.tsx +53 -24
- package/corpus/templates/clips/desktop/src/lib/bubble-playback.ts +68 -0
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +3 -0
- package/corpus/templates/clips/desktop/src/lib/transcription-capture.ts +72 -14
- package/corpus/templates/clips/desktop/src/lib/updater.ts +8 -3
- package/corpus/templates/clips/desktop/src/overlays/bubble.tsx +162 -20
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +43 -6
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +150 -13
- package/corpus/templates/clips/desktop/src-tauri/src/tray.rs +12 -0
- package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +28 -3
- package/corpus/templates/clips/server/lib/emails.ts +17 -33
- package/corpus/templates/content/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +140 -15
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +12 -14
- package/corpus/templates/content/app/components/ui/pointer-lock.ts +1 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +18 -2
- package/corpus/templates/content/app/hooks/use-local-storage.ts +38 -22
- package/corpus/templates/content/app/i18n-data.ts +25 -0
- package/corpus/templates/content/app/routes.ts +3 -1
- package/corpus/templates/content/changelog/2026-08-22-code-blocks-inserted-from-the-slash-menu-no-longer-get-stuck.md +6 -0
- package/corpus/templates/content/changelog/2026-08-22-the-sidebar-page-tree-no-longer-flashes-empty-after-creating-a-page.md +6 -0
- package/corpus/templates/content/docs/solutions/2026-08-21-content-mermaid-rendering-shape.md +233 -0
- package/corpus/templates/content/server/plugins/agent-chat.ts +9 -2
- package/corpus/templates/content/vitest.config.ts +7 -1
- package/corpus/templates/crm/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/design/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +1383 -88
- package/corpus/templates/design/actions/generate-design.ts +98 -47
- package/corpus/templates/design/actions/get-design-system.ts +6 -1
- package/corpus/templates/design/actions/refresh-design-system-with-builder.ts +146 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +70 -26
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +284 -136
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +2057 -141
- package/corpus/templates/design/app/components/design/editor/DesignBottomToolbar.tsx +6 -2
- package/corpus/templates/design/app/components/design/editor/DesignWorkspaceRail.tsx +4 -4
- package/corpus/templates/design/app/components/design/keyboard-shortcuts.ts +10 -1
- package/corpus/templates/design/app/components/design/multi-screen/alt-hover-measurement.ts +19 -2
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +9 -2
- package/corpus/templates/design/app/components/layout/Header.tsx +15 -0
- package/corpus/templates/design/app/components/layout/Layout.tsx +7 -2
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +11 -4
- package/corpus/templates/design/app/i18n-data.ts +1 -0
- package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +12 -0
- package/corpus/templates/design/app/lib/design-system-data.ts +68 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +77 -35
- package/corpus/templates/design/app/pages/DesignSystems.tsx +136 -42
- package/corpus/templates/design/app/pages/Index.tsx +2 -0
- package/corpus/templates/design/app/pages/Templates.tsx +0 -10
- package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +10 -0
- package/corpus/templates/design/app/pages/design-editor/collab-sync.ts +50 -0
- package/corpus/templates/design/app/pages/design-editor/commands/apply-design-editor-command.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/commands/apply-file-content-update.ts +10 -8
- package/corpus/templates/design/app/pages/design-editor/commands/apply-local-content-update.ts +6 -6
- package/corpus/templates/design/app/pages/design-editor/commands/apply-pending-visual-styles-with-agent.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +18 -7
- package/corpus/templates/design/app/pages/design-editor/commands/escape-hotkey.ts +4 -90
- package/corpus/templates/design/app/pages/design-editor/commands/layer-marquee-selection-change.ts +2 -1
- package/corpus/templates/design/app/pages/design-editor/commands/layer-selection-change.ts +4 -3
- package/corpus/templates/design/app/pages/design-editor/commands/screen-element-select.ts +12 -2
- package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-style-change.ts +9 -2
- package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-move-semantic-handoff.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-semantic-handoff.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-state-semantic-handoff.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/commands/start-sidebar-resize.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/editor-state.ts +11 -1
- package/corpus/templates/design/app/pages/design-editor/effects/adopt-db-file-content.ts +11 -19
- package/corpus/templates/design/app/pages/design-editor/effects/observe-collab-text.ts +6 -6
- package/corpus/templates/design/app/pages/design-editor/effects/publish-agent-selection-context.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/effects/seed-collab-content.ts +12 -15
- package/corpus/templates/design/app/pages/design-editor/selection-state.ts +5 -59
- package/corpus/templates/design/app/pages/design-editor/tool-state.ts +8 -0
- package/corpus/templates/design/app/pages/design-editor/types.ts +3 -3
- package/corpus/templates/design/app/routes.ts +3 -1
- package/corpus/templates/design/changelog/2026-08-20-canvas-edits-patch-the-frame-instead-of-rebuilding-it.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-builder-design-system-imports-reconcile-after-indexing.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-draw-mode-needs-shift-y.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-eyedropper-shortcut.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-figma-style-alignment-guides-and-snapping.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-group-transforms-keep-the-selection.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-inspector-fields-keep-focus-on-enter.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-pen-alt-keeps-the-incoming-handle.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-pen-on-the-board-creates-one-vector.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-scale-a-multi-selection-from-the-group-handles.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-scale-resizes-text-with-the-object.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-selecting-another-object-keeps-the-scale-tool-armed.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-the-shape-tool-remembers-the-shape-you-picked.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-vectors-drawn-in-a-frame-land-where-you-drew-them.md +6 -0
- package/corpus/templates/design/changelog/2026-08-22-multi-screen-generation-no-longer-loses-a-saved-screens-layo.md +6 -0
- package/corpus/templates/design/changelog/2026-08-22-rename-and-search-fields-are-labeled-for-screen-readers.md +6 -0
- package/corpus/templates/design/package.json +2 -0
- package/corpus/templates/design/server/lib/builder-design-system-proxy.ts +311 -0
- package/corpus/templates/design/shared/canvas-math.ts +612 -206
- package/corpus/templates/design/shared/pen-path.ts +37 -0
- package/corpus/templates/design/vitest.config.ts +7 -1
- package/corpus/templates/dispatch/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/dispatch/actions/list-workspace-connections.ts +34 -3
- package/corpus/templates/dispatch/app/routes/integrations.tsx +55 -21
- package/corpus/templates/dispatch/app/routes.ts +3 -1
- package/corpus/templates/factory/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/factory/.env.example +2 -7
- package/corpus/templates/factory/AGENTS.md +3 -0
- package/corpus/templates/factory/README.md +5 -3
- package/corpus/templates/factory/actions/create-factory.ts +16 -3
- package/corpus/templates/factory/actions/get-slack-feedback-context.ts +2 -0
- package/corpus/templates/factory/actions/get-triage-item.ts +2 -0
- package/corpus/templates/factory/actions/list-factory-audit.ts +116 -36
- package/corpus/templates/factory/actions/list-factory-automations.ts +2 -1
- package/corpus/templates/factory/actions/list-triage-items.ts +22 -19
- package/corpus/templates/factory/actions/run-factory-automation.ts +7 -3
- package/corpus/templates/factory/actions/save-factory-automation.ts +5 -2
- package/corpus/templates/factory/actions/save-triage-config.ts +2 -8
- package/corpus/templates/factory/actions/start-builder-for-item.ts +21 -0
- package/corpus/templates/factory/app/components/factory/FactoryAuditView.tsx +229 -297
- package/corpus/templates/factory/app/components/factory/FactorySettingsView.tsx +397 -247
- package/corpus/templates/factory/app/components/factory/FactorySourceSettingsGroup.tsx +33 -0
- package/corpus/templates/factory/app/components/factory/NewFactoryForm.tsx +202 -162
- package/corpus/templates/factory/app/i18n/en-US.ts +32 -4
- package/corpus/templates/factory/app/routes/factory.tsx +84 -11
- package/corpus/templates/factory/app/routes.ts +3 -1
- package/corpus/templates/factory/changelog/2026-08-21-activity-runs-use-automation-display-names.md +6 -0
- package/corpus/templates/factory/changelog/2026-08-21-create-and-settings-source-cards.md +6 -0
- package/corpus/templates/factory/changelog/2026-08-21-keep-automations-visible-while-running.md +6 -0
- package/corpus/templates/factory/changelog/2026-08-21-nested-factory-automations-can-poll.md +6 -0
- package/corpus/templates/factory/changelog/2026-08-21-settings-sticky-unsaved-bar.md +6 -0
- package/corpus/templates/factory/server/connectors/credentials.ts +10 -5
- package/corpus/templates/factory/server/lib/factory-audit-report.ts +412 -0
- package/corpus/templates/factory/server/lib/factory-config-reconcile.ts +321 -0
- package/corpus/templates/factory/server/lib/factory-scope.ts +25 -2
- package/corpus/templates/factory/server/lib/require-factory-automation.ts +13 -4
- package/corpus/templates/factory/server/plugins/factory-migrations.ts +148 -4
- package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +4 -6
- package/corpus/templates/factory/vitest.config.ts +17 -2
- package/corpus/templates/forms/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/forms/app/routes/_app.settings.tsx +4 -1
- package/corpus/templates/forms/changelog/2026-08-22-the-extensions-link-now-opens-the-extensions-tab-instead-o.md +6 -0
- package/corpus/templates/forms/server/lib/public-form-ssr.ts +11 -1
- package/corpus/templates/forms/vitest.config.ts +1 -0
- package/corpus/templates/macros/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/macros/app/routes.ts +3 -1
- package/corpus/templates/macros/vitest.config.ts +6 -1
- package/corpus/templates/mail/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/mail/actions/manage-gmail-filters.ts +10 -2
- package/corpus/templates/mail/app/components/GoogleConnectBanner.tsx +100 -37
- package/corpus/templates/mail/app/components/email/IntegrationsSidebar.tsx +14 -2
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +66 -36
- package/corpus/templates/mail/app/hooks/use-google-auth.ts +2 -0
- package/corpus/templates/mail/app/lib/google-oauth-setup.ts +17 -0
- package/corpus/templates/mail/app/pages/InboxPage.tsx +69 -9
- package/corpus/templates/mail/app/routes/$.tsx +9 -0
- package/corpus/templates/mail/app/routes/$view.$threadId.tsx +23 -1
- package/corpus/templates/mail/app/routes/$view.tsx +47 -1
- package/corpus/templates/mail/app/routes.ts +3 -1
- package/corpus/templates/mail/changelog/2026-08-22-gmail-can-now-connect-with-the-shared-google-oauth.md +6 -0
- package/corpus/templates/mail/changelog/2026-08-22-gmail-filters-now-shows-the-real-error.md +6 -0
- package/corpus/templates/mail/changelog/2026-08-22-unknown-mail-urls-now-show-the-404-page.md +6 -0
- package/corpus/templates/mail/server/handlers/google-auth.ts +11 -2
- package/corpus/templates/mail/server/lib/google-auth.ts +92 -12
- package/corpus/templates/mail/server/onboarding.ts +66 -18
- package/corpus/templates/mail/vitest.config.ts +6 -1
- package/corpus/templates/plan/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/plan/app/routes/settings.tsx +4 -1
- package/corpus/templates/plan/app/routes.ts +3 -1
- package/corpus/templates/plan/changelog/2026-08-22-the-extensions-link-now-opens-the-extensions-tab-instead-o.md +6 -0
- package/corpus/templates/plan/netlify.toml +1 -1
- package/corpus/templates/slides/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/slides/actions/patch-deck.ts +79 -10
- package/corpus/templates/slides/actions/update-slide.ts +11 -1
- package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +14 -4
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +10 -52
- package/corpus/templates/slides/app/components/editor/GoogleDocImportHint.tsx +8 -53
- package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +9 -75
- package/corpus/templates/slides/app/components/editor/GoogleSlidesReferenceImport.tsx +8 -60
- package/corpus/templates/slides/app/components/editor/SlideContextToolbar.tsx +10 -2
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +19 -7
- package/corpus/templates/slides/app/lib/sanitize-slide-html.ts +49 -36
- package/corpus/templates/slides/app/routes/access-request.approve.tsx +16 -9
- package/corpus/templates/slides/app/routes.ts +3 -1
- package/corpus/templates/slides/changelog/2026-08-22-already-uploaded-chat-images-are-no-longer-dropped.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-22-google-drive-and-slides-can-now-connect-with-the-shared-google-oauth.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-22-multi-slide-repairs-now-report-slides-that-still-overflow.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-22-update-slide-now-reports-which-batched-edit-was-skipped.md +6 -0
- package/corpus/templates/slides/server/handlers/google-docs-auth.ts +52 -1
- package/corpus/templates/slides/server/lib/chat-attachments.ts +29 -3
- package/corpus/templates/slides/server/lib/google-docs-oauth.ts +66 -16
- package/corpus/templates/slides/server/lib/slide-content-patch.ts +13 -2
- package/corpus/templates/slides/server/plugins/agent-chat.ts +5 -2
- package/corpus/templates/slides/tsconfig.json +5 -0
- package/corpus/templates/slides/vitest.config.ts +6 -1
- package/corpus/templates/tasks/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/tasks/app/routes/settings.tsx +4 -1
- package/corpus/templates/tasks/app/routes.ts +3 -1
- package/corpus/templates/tasks/changelog/2026-08-22-the-extensions-link-now-opens-the-extensions-tab-instead-o.md +6 -0
- package/corpus/templates/tasks/vitest.config.ts +7 -1
- package/dist/action.d.ts +91 -0
- package/dist/action.js +50 -0
- package/dist/agent/abort-reasons.d.ts +38 -0
- package/dist/agent/abort-reasons.js +49 -0
- package/dist/agent/engine/ai-sdk-engine.js +8 -5
- package/dist/agent/engine/anthropic-engine.js +7 -5
- package/dist/agent/engine/first-event-timeout.d.ts +42 -17
- package/dist/agent/engine/first-event-timeout.js +67 -25
- package/dist/agent/production-agent.d.ts +23 -0
- package/dist/agent/production-agent.js +107 -102
- package/dist/agent/run-loop-with-resume.d.ts +2 -28
- package/dist/agent/run-loop-with-resume.js +25 -40
- package/dist/agent/run-manager.js +5 -4
- package/dist/agent/run-store.d.ts +55 -0
- package/dist/agent/run-store.js +124 -1
- package/dist/agent/tool-search.js +2 -1
- package/dist/agent/types.d.ts +9 -4
- package/dist/app-config/configuration-error.d.ts +11 -0
- package/dist/app-config/configuration-error.js +14 -0
- package/dist/app-config/index.d.ts +1 -0
- package/dist/app-config/index.js +1 -0
- package/dist/app-config/integrations.d.ts +1 -0
- package/dist/app-config/integrations.js +10 -0
- package/dist/app-config/plugins.d.ts +34 -0
- package/dist/app-config/plugins.js +39 -0
- package/dist/app-config/run-lifecycle-invariants.d.ts +18 -9
- package/dist/app-config/run-lifecycle-invariants.js +18 -25
- package/dist/app-config/schema.d.ts +16 -0
- package/dist/app-config/schema.js +2 -0
- package/dist/chat-threads/schema-migrations.js +20 -0
- package/dist/chat-threads/store.js +21 -4
- package/dist/cli/native-dependencies.d.ts +4 -0
- package/dist/cli/native-dependencies.js +15 -0
- package/dist/client/EnvironmentBadge.d.ts +1 -1
- package/dist/client/EnvironmentBadge.js +40 -5
- package/dist/client/MultiTabAssistantChat.d.ts +1 -1
- package/dist/client/MultiTabAssistantChat.js +35 -14
- package/dist/client/agent-chat/index.d.ts +1 -1
- package/dist/client/agent-chat/index.js +1 -1
- package/dist/client/agent-page/use-jobs.d.ts +2 -1
- package/dist/client/agent-page/use-jobs.js +3 -5
- package/dist/client/agent-sidebar-state.js +3 -1
- package/dist/client/blocks/library/code-tabs.js +5 -0
- package/dist/client/blocks/library/code.js +1 -0
- package/dist/client/chat-first/apps-rail.js +2 -2
- package/dist/client/chat-model-groups.d.ts +12 -0
- package/dist/client/chat-model-groups.js +3 -2
- package/dist/client/integrations/GoogleProductLogo.d.ts +5 -0
- package/dist/client/integrations/GoogleProductLogo.js +45 -0
- package/dist/client/integrations/index.d.ts +2 -0
- package/dist/client/integrations/index.js +2 -0
- package/dist/client/integrations/workspace-provider-oauth.d.ts +8 -0
- package/dist/client/integrations/workspace-provider-oauth.js +13 -0
- package/dist/client/resources/mcp-integration-catalog.js +0 -29
- package/dist/client/settings/SettingsTabsPage.js +10 -6
- package/dist/client/sharing/ShareButton.js +11 -2
- package/dist/client/use-chat-models.d.ts +6 -0
- package/dist/client/use-chat-models.js +10 -0
- package/dist/client/use-db-sync.js +27 -9
- package/dist/connections/catalog.d.ts +111 -9
- package/dist/connections/catalog.js +86 -30
- package/dist/db/create-get-db.js +18 -0
- package/dist/db/ddl-guard.d.ts +0 -4
- package/dist/db/ddl-guard.js +46 -0
- package/dist/deploy/build.d.ts +19 -0
- package/dist/deploy/build.js +190 -16
- package/dist/deploy/function-bundle.d.ts +45 -0
- package/dist/deploy/function-bundle.js +337 -0
- package/dist/deploy/workspace-deploy.js +19 -1
- package/dist/eject/workspace-connections.js +4 -0
- package/dist/email-catalog/registry.d.ts +4 -0
- package/dist/email-catalog/registry.js +79 -14
- package/dist/extensions/content-patch.js +14 -2
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/integrations/plugin.d.ts +10 -0
- package/dist/integrations/plugin.js +40 -6
- package/dist/jobs/actions/run-automation-now.d.ts +2 -1
- package/dist/jobs/actions/run-automation-now.js +11 -5
- package/dist/jobs/frontmatter.js +53 -2
- package/dist/jobs/run-now.d.ts +11 -1
- package/dist/jobs/run-now.js +42 -7
- package/dist/jobs/scheduler.d.ts +1 -0
- package/dist/jobs/scheduler.js +2 -1
- package/dist/localization/core-messages/ar-SA.js +3 -0
- package/dist/localization/core-messages/de-DE.js +3 -0
- package/dist/localization/core-messages/en-US.d.ts +3 -0
- package/dist/localization/core-messages/en-US.js +3 -0
- package/dist/localization/core-messages/es-ES.js +3 -0
- package/dist/localization/core-messages/fr-FR.js +3 -0
- package/dist/localization/core-messages/hi-IN.js +3 -0
- package/dist/localization/core-messages/ja-JP.js +3 -0
- package/dist/localization/core-messages/ko-KR.js +3 -0
- package/dist/localization/core-messages/pt-BR.js +3 -0
- package/dist/localization/core-messages/zh-CN.js +3 -0
- package/dist/localization/core-messages/zh-TW.js +3 -0
- package/dist/localization/core-messages.d.ts +3 -0
- package/dist/localization/default-messages.d.ts +2 -5
- package/dist/localization/default-messages.js +2 -5
- package/dist/mcp/build-server.d.ts +9 -0
- package/dist/mcp/build-server.js +37 -8
- package/dist/mcp-client/oauth-client.d.ts +7 -0
- package/dist/mcp-client/oauth-client.js +12 -1
- package/dist/oauth-tokens/index.d.ts +1 -1
- package/dist/oauth-tokens/index.js +1 -1
- package/dist/oauth-tokens/lifecycle.d.ts +11 -0
- package/dist/oauth-tokens/lifecycle.js +17 -0
- package/dist/oauth-tokens/store.js +7 -0
- package/dist/observability/posthog-ai.d.ts +15 -1
- package/dist/observability/posthog-ai.js +24 -12
- package/dist/observability/routes.d.ts +3 -3
- package/dist/observability/traces.js +158 -28
- package/dist/onboarding/app-profile.js +2 -2
- package/dist/onboarding/plugin.js +10 -2
- package/dist/onboarding/types.d.ts +2 -0
- package/dist/provider-api/actions/custom-provider-registration.d.ts +1 -1
- package/dist/provider-api/actions/provider-api.d.ts +2 -2
- package/dist/provider-api/index.js +2 -0
- package/dist/resources/store.js +13 -4
- package/dist/scripts/agent-engines/list-agent-engines.js +29 -3
- package/dist/secrets/register-framework-secrets.js +1 -1
- package/dist/server/action-discovery.js +5 -0
- package/dist/server/agent-chat/action-filters-a2a.d.ts +27 -3
- package/dist/server/agent-chat/action-filters-a2a.js +48 -9
- package/dist/server/agent-chat-plugin.js +36 -9
- package/dist/server/agent-discovery.js +43 -20
- package/dist/server/auth.js +18 -16
- package/dist/server/beta-opt-out-html.js +45 -4
- package/dist/server/better-auth-instance.d.ts +2 -1
- package/dist/server/better-auth-instance.js +23 -7
- package/dist/server/builder-design-systems.d.ts +4 -1
- package/dist/server/builder-design-systems.js +87 -6
- package/dist/server/builder-oauth.d.ts +1 -7
- package/dist/server/builder-oauth.js +80 -163
- package/dist/server/core-routes-plugin.d.ts +29 -0
- package/dist/server/core-routes-plugin.js +100 -20
- package/dist/server/default-plugins.d.ts +18 -0
- package/dist/server/default-plugins.js +22 -0
- package/dist/server/edge.d.ts +1 -0
- package/dist/server/edge.js +1 -0
- package/dist/server/framework-request-handler.js +31 -3
- package/dist/server/google-oauth.d.ts +3 -0
- package/dist/server/google-oauth.js +3 -0
- package/dist/server/index.d.ts +4 -4
- package/dist/server/index.js +3 -3
- package/dist/server/onboarding-html.js +37 -3
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/server/workspace-provider-oauth.d.ts +6 -1
- package/dist/server/workspace-provider-oauth.js +72 -20
- package/dist/shared/agent-sidebar-url.d.ts +1 -0
- package/dist/shared/agent-sidebar-url.js +1 -0
- package/dist/shared/environment-lanes.d.ts +2 -0
- package/dist/shared/environment-lanes.js +3 -1
- package/dist/shared/index.d.ts +3 -1
- package/dist/shared/index.js +3 -1
- package/dist/shared/surface-visibility.d.ts +31 -0
- package/dist/shared/surface-visibility.js +57 -0
- package/dist/templates/chat/.agents/skills/secrets/SKILL.md +11 -4
- package/dist/templates/chat/app/routes.ts +3 -1
- package/dist/templates/chat/vitest.config.ts +8 -0
- package/dist/templates/default/.agents/skills/secrets/SKILL.md +11 -4
- package/dist/templates/default/app/routes.ts +3 -1
- package/dist/templates/headless/.agents/skills/secrets/SKILL.md +11 -4
- package/dist/templates/workspace-core/.agents/skills/secrets/SKILL.md +11 -4
- package/dist/tracking/registry.d.ts +11 -0
- package/dist/tracking/registry.js +5 -2
- package/dist/triggers/actions.js +10 -3
- package/dist/usage/store.js +23 -3
- package/dist/user-profile/actions/get-auth-methods.js +18 -5
- package/dist/vite/client.js +24 -5
- package/docs/content/actions-access-control.mdx +43 -4
- package/docs/content/actions-defining.mdx +16 -0
- package/docs/content/template-clips-capture-everywhere.mdx +29 -30
- package/docs/content/template-clips-developers.mdx +4 -4
- package/docs/content/template-clips.mdx +3 -1
- package/package.json +2 -2
- package/src/eject/workspace-connections.ts +4 -0
- package/src/templates/chat/.agents/skills/secrets/SKILL.md +11 -4
- package/src/templates/chat/app/routes.ts +3 -1
- package/src/templates/chat/vitest.config.ts +8 -0
- package/src/templates/default/.agents/skills/secrets/SKILL.md +11 -4
- package/src/templates/default/app/routes.ts +3 -1
- package/src/templates/headless/.agents/skills/secrets/SKILL.md +11 -4
- package/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +11 -4
|
@@ -8,9 +8,11 @@ import {
|
|
|
8
8
|
DEFAULT_CANVAS_MIN_ZOOM,
|
|
9
9
|
DEFAULT_SNAP_THRESHOLD_SCREEN_PX,
|
|
10
10
|
canvasToScreenPoint,
|
|
11
|
-
|
|
11
|
+
computeDragSnap,
|
|
12
12
|
computeMoveSnap,
|
|
13
13
|
computeResizeSnap,
|
|
14
|
+
type DistanceGuideBand,
|
|
15
|
+
type ProximityMeasurement,
|
|
14
16
|
type EqualGapGuide,
|
|
15
17
|
getCameraForBounds,
|
|
16
18
|
getFrameGroupBounds,
|
|
@@ -31,7 +33,8 @@ import {
|
|
|
31
33
|
closePenPath,
|
|
32
34
|
constrainPointTo45Degrees,
|
|
33
35
|
createCornerNode,
|
|
34
|
-
|
|
36
|
+
createPenCuspLatch,
|
|
37
|
+
createPenDragNode,
|
|
35
38
|
getPenPathGeometry,
|
|
36
39
|
hitTestPenAnchor,
|
|
37
40
|
hitTestPenHandle,
|
|
@@ -142,9 +145,6 @@ const SCREEN_CARD_HEIGHT = SCREEN_HEIGHT + 26;
|
|
|
142
145
|
const SCREEN_GAP = 56;
|
|
143
146
|
const DUPLICATE_DRAG_THRESHOLD = 6;
|
|
144
147
|
const DRAG_THRESHOLD = 3;
|
|
145
|
-
/** How close two gaps must be (in screen px, converted to canvas px at the
|
|
146
|
-
* live zoom) to count as "equal" for the smart-spacing guides (CV11). */
|
|
147
|
-
const EQUAL_GAP_TOLERANCE_SCREEN_PX = 2;
|
|
148
148
|
const FRAME_LABEL_HEIGHT = 28;
|
|
149
149
|
const FRAME_HEADER_BUTTON_COMPACT_WIDTH = 260;
|
|
150
150
|
const FRAME_HEADER_BUTTON_RESERVE = 116;
|
|
@@ -284,6 +284,7 @@ import {
|
|
|
284
284
|
altHoverMeasurementEqual,
|
|
285
285
|
computeAltHoverMeasurement,
|
|
286
286
|
equalGapGuidesEqual,
|
|
287
|
+
proximityMeasurementsEqual,
|
|
287
288
|
} from "./multi-screen/alt-hover-measurement";
|
|
288
289
|
import {
|
|
289
290
|
boardPointToScreenLocalPoint,
|
|
@@ -306,6 +307,8 @@ import {
|
|
|
306
307
|
computeBoundedScreenCullState,
|
|
307
308
|
getScreenContentCullState,
|
|
308
309
|
getOverscannedViewportCanvasBounds,
|
|
310
|
+
isFrameWithinOverscannedViewport,
|
|
311
|
+
type OverscannedViewportBounds,
|
|
309
312
|
type ScreenCullTier,
|
|
310
313
|
} from "./multi-screen/culling";
|
|
311
314
|
import {
|
|
@@ -835,6 +838,9 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
835
838
|
[],
|
|
836
839
|
);
|
|
837
840
|
const [equalGapGuides, setEqualGapGuidesRaw] = useState<EqualGapGuide[]>([]);
|
|
841
|
+
const [proximityMeasurements, setProximityMeasurementsRaw] = useState<
|
|
842
|
+
ProximityMeasurement[]
|
|
843
|
+
>([]);
|
|
838
844
|
// Guides are recomputed into a brand-new array on every rAF-coalesced
|
|
839
845
|
// mousemove during a drag; without a value-equality bail, React commits a
|
|
840
846
|
// state update (and a re-render) every frame even when the guides drawn on
|
|
@@ -844,6 +850,14 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
844
850
|
alignmentGuidesEqual(current, next) ? current : next,
|
|
845
851
|
);
|
|
846
852
|
}, []);
|
|
853
|
+
const setProximityMeasurements = useCallback(
|
|
854
|
+
(next: ProximityMeasurement[]) => {
|
|
855
|
+
setProximityMeasurementsRaw((current) =>
|
|
856
|
+
proximityMeasurementsEqual(current, next) ? current : next,
|
|
857
|
+
);
|
|
858
|
+
},
|
|
859
|
+
[],
|
|
860
|
+
);
|
|
847
861
|
const setEqualGapGuides = useCallback((next: EqualGapGuide[]) => {
|
|
848
862
|
setEqualGapGuidesRaw((current) =>
|
|
849
863
|
equalGapGuidesEqual(current, next) ? current : next,
|
|
@@ -1752,6 +1766,47 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
1752
1766
|
[getCurrentDraftEntries, getCurrentFrameEntries],
|
|
1753
1767
|
);
|
|
1754
1768
|
|
|
1769
|
+
/** Snap candidates are the entries on screen right now, matching Figma and
|
|
1770
|
+
* tldraw: a frame parked off in the corner of an infinite canvas is not
|
|
1771
|
+
* something the user is aligning to, and a guide drawn to it would stretch
|
|
1772
|
+
* across empty space. Falls back to every entry before first layout. */
|
|
1773
|
+
const snapViewportRef = useRef<OverscannedViewportBounds | null>(null);
|
|
1774
|
+
|
|
1775
|
+
/** Reads the surface box once per gesture. Called per rAF it would force a
|
|
1776
|
+
* synchronous layout of the host document — which holds every live screen
|
|
1777
|
+
* iframe — on the hottest path there is. Pan/zoom cannot change mid-drag. */
|
|
1778
|
+
const beginSnapGesture = useCallback(() => {
|
|
1779
|
+
const size = surfaceSizeRef.current;
|
|
1780
|
+
snapViewportRef.current = size
|
|
1781
|
+
? getOverscannedViewportCanvasBounds(
|
|
1782
|
+
size,
|
|
1783
|
+
panRef.current,
|
|
1784
|
+
zoomRef.current,
|
|
1785
|
+
0,
|
|
1786
|
+
)
|
|
1787
|
+
: null;
|
|
1788
|
+
}, []);
|
|
1789
|
+
|
|
1790
|
+
/** Wheel pan/zoom is not blocked during a drag, so the cached viewport can
|
|
1791
|
+
* go stale mid-gesture; drop it and fall back to every candidate rather
|
|
1792
|
+
* than filter against a rect that no longer describes the screen. */
|
|
1793
|
+
const invalidateSnapGesture = useCallback(() => {
|
|
1794
|
+
snapViewportRef.current = null;
|
|
1795
|
+
}, []);
|
|
1796
|
+
|
|
1797
|
+
const getSnapCandidateEntries = useCallback(
|
|
1798
|
+
(excludeIds: string[], entries = getCurrentCanvasEntries()) => {
|
|
1799
|
+
const viewport = snapViewportRef.current;
|
|
1800
|
+
return entries.filter(
|
|
1801
|
+
(entry) =>
|
|
1802
|
+
!excludeIds.includes(entry.id) &&
|
|
1803
|
+
(!viewport ||
|
|
1804
|
+
isFrameWithinOverscannedViewport(entry.geometry, viewport)),
|
|
1805
|
+
);
|
|
1806
|
+
},
|
|
1807
|
+
[getCurrentCanvasEntries],
|
|
1808
|
+
);
|
|
1809
|
+
|
|
1755
1810
|
const getFrameEntryAtPoint = useCallback(
|
|
1756
1811
|
(point: Point) =>
|
|
1757
1812
|
findTopFrameEntryAtPoint(getSelectableFrameEntries(), point, {
|
|
@@ -3070,6 +3125,53 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
3070
3125
|
],
|
|
3071
3126
|
);
|
|
3072
3127
|
|
|
3128
|
+
/** Selects the layer under a client point inside one screen. Shared by the
|
|
3129
|
+
* double-click drill-in and by a click on an already-selected frame's body,
|
|
3130
|
+
* so both resolve the same candidate and keep the same repeat-click walk. */
|
|
3131
|
+
const drillIntoScreenAtPoint = useCallback(
|
|
3132
|
+
(id: string, clientX: number, clientY: number) => {
|
|
3133
|
+
const point = getCanvasPoint(clientX, clientY);
|
|
3134
|
+
const previousKey =
|
|
3135
|
+
drillInTargetRef.current?.screenId === id
|
|
3136
|
+
? drillInTargetRef.current.key
|
|
3137
|
+
: null;
|
|
3138
|
+
const requestId = drillInRequestRef.current + 1;
|
|
3139
|
+
drillInRequestRef.current = requestId;
|
|
3140
|
+
void collectLayerMarqueeCandidates(new Set([id])).then((candidates) => {
|
|
3141
|
+
if (drillInRequestRef.current !== requestId) return;
|
|
3142
|
+
const target = resolveDrillInTarget({
|
|
3143
|
+
candidates,
|
|
3144
|
+
screenId: id,
|
|
3145
|
+
point,
|
|
3146
|
+
previousKey,
|
|
3147
|
+
});
|
|
3148
|
+
if (!target) {
|
|
3149
|
+
// Nothing selectable under the pointer (e.g. an empty frame). Leave
|
|
3150
|
+
// the frame itself selected rather than falling back to Interact.
|
|
3151
|
+
drillInTargetRef.current = null;
|
|
3152
|
+
return;
|
|
3153
|
+
}
|
|
3154
|
+
drillInTargetRef.current = {
|
|
3155
|
+
screenId: id,
|
|
3156
|
+
key: drillInCandidateKey(target),
|
|
3157
|
+
};
|
|
3158
|
+
updateSelectedDraftIds(() => []);
|
|
3159
|
+
updateSelectedIds(() => []);
|
|
3160
|
+
onLayerMarqueeSelectionChange?.(
|
|
3161
|
+
[{ screenId: target.screenId, info: target.info }],
|
|
3162
|
+
{ source: "pointer" },
|
|
3163
|
+
);
|
|
3164
|
+
});
|
|
3165
|
+
},
|
|
3166
|
+
[
|
|
3167
|
+
collectLayerMarqueeCandidates,
|
|
3168
|
+
getCanvasPoint,
|
|
3169
|
+
onLayerMarqueeSelectionChange,
|
|
3170
|
+
updateSelectedDraftIds,
|
|
3171
|
+
updateSelectedIds,
|
|
3172
|
+
],
|
|
3173
|
+
);
|
|
3174
|
+
|
|
3073
3175
|
const scheduleFeedbackClear = useCallback(() => {
|
|
3074
3176
|
if (feedbackTimerRef.current !== null) {
|
|
3075
3177
|
window.clearTimeout(feedbackTimerRef.current);
|
|
@@ -3232,6 +3334,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
3232
3334
|
setCreationPreview(null);
|
|
3233
3335
|
setAlignmentGuides([]);
|
|
3234
3336
|
setEqualGapGuides([]);
|
|
3337
|
+
setProximityMeasurements([]);
|
|
3235
3338
|
setTransformBadge(null);
|
|
3236
3339
|
setDragCursor(null);
|
|
3237
3340
|
primitiveDropTargetRef.current = null;
|
|
@@ -3762,6 +3865,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
3762
3865
|
(
|
|
3763
3866
|
draft: DraftPrimitive,
|
|
3764
3867
|
preferredFrameId?: string,
|
|
3868
|
+
options?: { nextTool?: "move" | "pen" },
|
|
3765
3869
|
): PersistedDraftPrimitive | null => {
|
|
3766
3870
|
const targetFrame = getTargetFrameForDraft(draft, preferredFrameId);
|
|
3767
3871
|
|
|
@@ -3779,7 +3883,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
3779
3883
|
width: 1,
|
|
3780
3884
|
height: 1,
|
|
3781
3885
|
});
|
|
3782
|
-
const persisted = handler(boardPrimitive);
|
|
3886
|
+
const persisted = handler(boardPrimitive, options);
|
|
3783
3887
|
if (!persisted) return null;
|
|
3784
3888
|
// Return the board file id so the caller can run the same selection
|
|
3785
3889
|
// and text-edit activation path used by regular screen primitives.
|
|
@@ -3856,7 +3960,11 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
3856
3960
|
preferredFrameId?: string,
|
|
3857
3961
|
options?: { nextTool?: "move" | "pen" },
|
|
3858
3962
|
) => {
|
|
3859
|
-
const persisted = persistDraftPrimitive(
|
|
3963
|
+
const persisted = persistDraftPrimitive(
|
|
3964
|
+
nextDraft,
|
|
3965
|
+
preferredFrameId,
|
|
3966
|
+
options,
|
|
3967
|
+
);
|
|
3860
3968
|
if (persisted) {
|
|
3861
3969
|
updateDraftPrimitives((current) =>
|
|
3862
3970
|
current.filter((draft) => draft.id !== nextDraft.id),
|
|
@@ -3947,10 +4055,10 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
3947
4055
|
|
|
3948
4056
|
const finishPenPath = useCallback(
|
|
3949
4057
|
(path = activePenPathRef.current) => {
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
4058
|
+
// Clear before committing: the commit flushes React synchronously, and
|
|
4059
|
+
// an effect it wakes can re-enter here and commit the same path twice.
|
|
4060
|
+
clearActivePenPath();
|
|
4061
|
+
if (!path || path.nodes.length < 2) return;
|
|
3954
4062
|
|
|
3955
4063
|
commitDraftPrimitive(
|
|
3956
4064
|
createPenDraftPrimitive(path, {
|
|
@@ -3966,7 +4074,6 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
3966
4074
|
// asynchronous selection reconciliation can otherwise paint Move for a
|
|
3967
4075
|
// frame. Drive the controlled tool explicitly at the commit boundary.
|
|
3968
4076
|
onActiveToolChange?.("pen");
|
|
3969
|
-
clearActivePenPath();
|
|
3970
4077
|
},
|
|
3971
4078
|
[clearActivePenPath, commitDraftPrimitive, onActiveToolChange, toolProps],
|
|
3972
4079
|
);
|
|
@@ -4096,6 +4203,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
4096
4203
|
pathBefore: pathSnapshot,
|
|
4097
4204
|
hasMoved: false,
|
|
4098
4205
|
closing,
|
|
4206
|
+
cuspLatch: createPenCuspLatch(),
|
|
4099
4207
|
};
|
|
4100
4208
|
const initialPath = closing
|
|
4101
4209
|
? (pathSnapshot as PenPath)
|
|
@@ -4142,13 +4250,12 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
4142
4250
|
}
|
|
4143
4251
|
|
|
4144
4252
|
const node = state.hasMoved
|
|
4145
|
-
?
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
})
|
|
4253
|
+
? createPenDragNode(
|
|
4254
|
+
state.anchor,
|
|
4255
|
+
handleOut,
|
|
4256
|
+
state.cuspLatch,
|
|
4257
|
+
ev.altKey,
|
|
4258
|
+
)
|
|
4152
4259
|
: createCornerNode(state.anchor);
|
|
4153
4260
|
const nextPath = appendPenNode(state.pathBefore, node);
|
|
4154
4261
|
activePenPathRef.current = nextPath;
|
|
@@ -4182,9 +4289,12 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
4182
4289
|
}
|
|
4183
4290
|
|
|
4184
4291
|
const node: PenNode = state.hasMoved
|
|
4185
|
-
?
|
|
4186
|
-
|
|
4187
|
-
|
|
4292
|
+
? createPenDragNode(
|
|
4293
|
+
state.anchor,
|
|
4294
|
+
handleOut,
|
|
4295
|
+
state.cuspLatch,
|
|
4296
|
+
ev.altKey,
|
|
4297
|
+
)
|
|
4188
4298
|
: createCornerNode(state.anchor);
|
|
4189
4299
|
const nextPath = appendPenNode(state.pathBefore, node);
|
|
4190
4300
|
activePenPathRef.current = nextPath;
|
|
@@ -4331,6 +4441,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
4331
4441
|
which,
|
|
4332
4442
|
pathBefore,
|
|
4333
4443
|
hasMoved: false,
|
|
4444
|
+
symmetryBroken: false,
|
|
4334
4445
|
};
|
|
4335
4446
|
setIsDragging(true);
|
|
4336
4447
|
setDragCursor("crosshair");
|
|
@@ -4354,15 +4465,13 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
4354
4465
|
canvasPoint,
|
|
4355
4466
|
active.originCanvas,
|
|
4356
4467
|
);
|
|
4357
|
-
|
|
4358
|
-
// matching the pen tool's own alt behavior (read live on every move
|
|
4359
|
-
// so toggling Alt mid-drag updates immediately).
|
|
4468
|
+
if (ev.altKey) state.symmetryBroken = true;
|
|
4360
4469
|
const nextPath = movePenHandle(
|
|
4361
4470
|
state.pathBefore,
|
|
4362
4471
|
state.nodeIndex,
|
|
4363
4472
|
state.which,
|
|
4364
4473
|
localPoint,
|
|
4365
|
-
{ breakSymmetry:
|
|
4474
|
+
{ breakSymmetry: state.symmetryBroken },
|
|
4366
4475
|
);
|
|
4367
4476
|
active.onChange(nextPath, "preview");
|
|
4368
4477
|
};
|
|
@@ -4383,13 +4492,14 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
4383
4492
|
canvasPoint,
|
|
4384
4493
|
active.originCanvas,
|
|
4385
4494
|
);
|
|
4495
|
+
if (ev.altKey) state.symmetryBroken = true;
|
|
4386
4496
|
const nextPath = state.hasMoved
|
|
4387
4497
|
? movePenHandle(
|
|
4388
4498
|
state.pathBefore,
|
|
4389
4499
|
state.nodeIndex,
|
|
4390
4500
|
state.which,
|
|
4391
4501
|
localPoint,
|
|
4392
|
-
{ breakSymmetry:
|
|
4502
|
+
{ breakSymmetry: state.symmetryBroken },
|
|
4393
4503
|
)
|
|
4394
4504
|
: state.pathBefore;
|
|
4395
4505
|
active.onChange(nextPath, "commit");
|
|
@@ -4655,6 +4765,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
4655
4765
|
current.includes(id) ? current : [id],
|
|
4656
4766
|
);
|
|
4657
4767
|
|
|
4768
|
+
beginSnapGesture();
|
|
4658
4769
|
dragState.current = {
|
|
4659
4770
|
type: "draft-move",
|
|
4660
4771
|
originClient: { x: e.clientX, y: e.clientY },
|
|
@@ -4730,13 +4841,13 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
4730
4841
|
},
|
|
4731
4842
|
};
|
|
4732
4843
|
});
|
|
4733
|
-
const stationaryEntries =
|
|
4734
|
-
|
|
4735
|
-
);
|
|
4736
|
-
const snap = computeMoveSnap(movingEntries, stationaryEntries, {
|
|
4844
|
+
const stationaryEntries = getSnapCandidateEntries(state.targetIds);
|
|
4845
|
+
const snap = computeDragSnap(movingEntries, stationaryEntries, {
|
|
4737
4846
|
thresholdScreenPx: DEFAULT_SNAP_THRESHOLD_SCREEN_PX,
|
|
4738
4847
|
zoom: zoomRef.current,
|
|
4739
4848
|
bypass: ev.metaKey || ev.ctrlKey,
|
|
4849
|
+
pixelGrid: true,
|
|
4850
|
+
lockedAxes: ev.shiftKey ? { x: dx === 0, y: dy === 0 } : undefined,
|
|
4740
4851
|
});
|
|
4741
4852
|
|
|
4742
4853
|
// PERF9: ref-only geometry write (no setDraftPrimitives) + direct DOM
|
|
@@ -4793,6 +4904,8 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
4793
4904
|
}
|
|
4794
4905
|
}
|
|
4795
4906
|
setAlignmentGuides(snap.guides);
|
|
4907
|
+
setEqualGapGuides(snap.spacingGuides);
|
|
4908
|
+
setProximityMeasurements(snap.measurements);
|
|
4796
4909
|
|
|
4797
4910
|
// Primitive drop-into-container detection: check if the dragged draft
|
|
4798
4911
|
// is hovering over a committed container primitive on any screen.
|
|
@@ -5411,6 +5524,10 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
5411
5524
|
const targetIds = currentSelectedIds.includes(id)
|
|
5412
5525
|
? currentSelectedIds
|
|
5413
5526
|
: [id];
|
|
5527
|
+
// Figma parity: this surface covers the frame body, so a press that
|
|
5528
|
+
// never becomes a drag has to hand the click back to the content
|
|
5529
|
+
// underneath — otherwise selecting a screen makes it unclickable.
|
|
5530
|
+
const wasAlreadySelected = currentSelectedIds.includes(id);
|
|
5414
5531
|
if (activeId !== id) {
|
|
5415
5532
|
onPick(id);
|
|
5416
5533
|
}
|
|
@@ -5427,6 +5544,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
5427
5544
|
) as FrameGeometryById;
|
|
5428
5545
|
if (!originFrames[id]) return;
|
|
5429
5546
|
|
|
5547
|
+
beginSnapGesture();
|
|
5430
5548
|
dragState.current = {
|
|
5431
5549
|
type: "move",
|
|
5432
5550
|
originClient: { x: e.clientX, y: e.clientY },
|
|
@@ -5528,13 +5646,18 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
5528
5646
|
y: state.originFrames[targetId].y + dy,
|
|
5529
5647
|
},
|
|
5530
5648
|
}));
|
|
5531
|
-
|
|
5532
|
-
|
|
5649
|
+
// Frames align to frames: a draft primitive is transient content and
|
|
5650
|
+
// must not anchor a persisted screen move.
|
|
5651
|
+
const stationaryEntries = getSnapCandidateEntries(
|
|
5652
|
+
state.targetIds,
|
|
5653
|
+
getCurrentFrameEntries(),
|
|
5533
5654
|
);
|
|
5534
|
-
const snap =
|
|
5655
|
+
const snap = computeDragSnap(movingEntries, stationaryEntries, {
|
|
5535
5656
|
thresholdScreenPx: DEFAULT_SNAP_THRESHOLD_SCREEN_PX,
|
|
5536
5657
|
zoom: zoomRef.current,
|
|
5537
5658
|
bypass: ev.metaKey || ev.ctrlKey,
|
|
5659
|
+
pixelGrid: true,
|
|
5660
|
+
lockedAxes: ev.shiftKey ? { x: dx === 0, y: dy === 0 } : undefined,
|
|
5538
5661
|
});
|
|
5539
5662
|
|
|
5540
5663
|
// PERF9: mutate the dragged frame(s)' DOM position directly (ref-only
|
|
@@ -5624,37 +5747,8 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
5624
5747
|
}
|
|
5625
5748
|
}
|
|
5626
5749
|
setAlignmentGuides(snap.guides);
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
// frame (matches Figma, which only shows equal-gap guides while
|
|
5630
|
-
// dragging one object, not a multi-select group).
|
|
5631
|
-
if (state.targetIds.length === 1) {
|
|
5632
|
-
const primaryId = state.targetIds[0];
|
|
5633
|
-
const movedGeometry = movingEntries.find(
|
|
5634
|
-
(entry) => entry.id === primaryId,
|
|
5635
|
-
)?.geometry;
|
|
5636
|
-
if (movedGeometry) {
|
|
5637
|
-
// Same screen-px-to-canvas-px conversion computeMoveSnap uses
|
|
5638
|
-
// for its own threshold, so the equal-gap tolerance also stays
|
|
5639
|
-
// a constant few screen pixels regardless of zoom level.
|
|
5640
|
-
const tolerance =
|
|
5641
|
-
EQUAL_GAP_TOLERANCE_SCREEN_PX /
|
|
5642
|
-
Math.max(0.01, zoomRef.current / 100);
|
|
5643
|
-
setEqualGapGuides(
|
|
5644
|
-
computeEqualGapGuides(
|
|
5645
|
-
{
|
|
5646
|
-
...movedGeometry,
|
|
5647
|
-
x: movedGeometry.x + snap.dx,
|
|
5648
|
-
y: movedGeometry.y + snap.dy,
|
|
5649
|
-
},
|
|
5650
|
-
stationaryEntries,
|
|
5651
|
-
{ toleranceCanvasPx: tolerance },
|
|
5652
|
-
),
|
|
5653
|
-
);
|
|
5654
|
-
}
|
|
5655
|
-
} else {
|
|
5656
|
-
setEqualGapGuides([]);
|
|
5657
|
-
}
|
|
5750
|
+
setEqualGapGuides(snap.spacingGuides);
|
|
5751
|
+
setProximityMeasurements(snap.measurements);
|
|
5658
5752
|
|
|
5659
5753
|
// Resize shows a W x H badge and rotate shows a degrees badge — move
|
|
5660
5754
|
// was the one transform with no live feedback at all. Show the
|
|
@@ -5687,17 +5781,19 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
5687
5781
|
}
|
|
5688
5782
|
};
|
|
5689
5783
|
|
|
5690
|
-
const handleMouseUp = () => {
|
|
5784
|
+
const handleMouseUp = (ev: MouseEvent) => {
|
|
5691
5785
|
const state = dragState.current;
|
|
5692
5786
|
const dropTarget = primitiveDropTargetRef.current;
|
|
5693
5787
|
if (state?.type === "move" && !state.hasMoved) {
|
|
5694
|
-
// Belt-and-braces
|
|
5695
|
-
//
|
|
5696
|
-
//
|
|
5697
|
-
// directly) so a below-threshold click never leaves a phantom nudge.
|
|
5788
|
+
// Belt-and-braces against a below-threshold click leaving a phantom
|
|
5789
|
+
// nudge, in case any geometry slipped past the live transform's own
|
|
5790
|
+
// hasMoved guard.
|
|
5698
5791
|
updateFrameGeometry((current) =>
|
|
5699
5792
|
frameGeometryWithOverrides(current, state.originFrames),
|
|
5700
5793
|
);
|
|
5794
|
+
if (wasAlreadySelected) {
|
|
5795
|
+
drillIntoScreenAtPoint(id, ev.clientX, ev.clientY);
|
|
5796
|
+
}
|
|
5701
5797
|
}
|
|
5702
5798
|
if (state?.type === "move" && state.hasMoved) {
|
|
5703
5799
|
// If all dragged ids are committed primitive nodeIds (not screen
|
|
@@ -5751,6 +5847,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
5751
5847
|
[
|
|
5752
5848
|
activeId,
|
|
5753
5849
|
beginDuplicateGesture,
|
|
5850
|
+
drillIntoScreenAtPoint,
|
|
5754
5851
|
findPrimitiveDropTarget,
|
|
5755
5852
|
finishDrag,
|
|
5756
5853
|
getCanvasPoint,
|
|
@@ -6562,48 +6659,9 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
6562
6659
|
onEdit?.(id);
|
|
6563
6660
|
return;
|
|
6564
6661
|
}
|
|
6565
|
-
|
|
6566
|
-
const previousKey =
|
|
6567
|
-
drillInTargetRef.current?.screenId === id
|
|
6568
|
-
? drillInTargetRef.current.key
|
|
6569
|
-
: null;
|
|
6570
|
-
const requestId = drillInRequestRef.current + 1;
|
|
6571
|
-
drillInRequestRef.current = requestId;
|
|
6572
|
-
void collectLayerMarqueeCandidates(new Set([id])).then((candidates) => {
|
|
6573
|
-
if (drillInRequestRef.current !== requestId) return;
|
|
6574
|
-
const target = resolveDrillInTarget({
|
|
6575
|
-
candidates,
|
|
6576
|
-
screenId: id,
|
|
6577
|
-
point,
|
|
6578
|
-
previousKey,
|
|
6579
|
-
});
|
|
6580
|
-
if (!target) {
|
|
6581
|
-
// Nothing selectable under the pointer (e.g. an empty frame). Leave
|
|
6582
|
-
// the frame itself selected rather than falling back to Interact.
|
|
6583
|
-
drillInTargetRef.current = null;
|
|
6584
|
-
return;
|
|
6585
|
-
}
|
|
6586
|
-
drillInTargetRef.current = {
|
|
6587
|
-
screenId: id,
|
|
6588
|
-
key: drillInCandidateKey(target),
|
|
6589
|
-
};
|
|
6590
|
-
updateSelectedDraftIds(() => []);
|
|
6591
|
-
updateSelectedIds(() => []);
|
|
6592
|
-
onLayerMarqueeSelectionChange?.(
|
|
6593
|
-
[{ screenId: target.screenId, info: target.info }],
|
|
6594
|
-
{ source: "pointer" },
|
|
6595
|
-
);
|
|
6596
|
-
});
|
|
6662
|
+
drillIntoScreenAtPoint(id, e.clientX, e.clientY);
|
|
6597
6663
|
},
|
|
6598
|
-
[
|
|
6599
|
-
collectLayerMarqueeCandidates,
|
|
6600
|
-
getCanvasPoint,
|
|
6601
|
-
lockedScreenIdSet,
|
|
6602
|
-
onEdit,
|
|
6603
|
-
onLayerMarqueeSelectionChange,
|
|
6604
|
-
updateSelectedDraftIds,
|
|
6605
|
-
updateSelectedIds,
|
|
6606
|
-
],
|
|
6664
|
+
[drillIntoScreenAtPoint, lockedScreenIdSet, onEdit],
|
|
6607
6665
|
);
|
|
6608
6666
|
|
|
6609
6667
|
const handleMouseDown = useCallback(
|
|
@@ -7229,6 +7287,8 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
7229
7287
|
// Real user wheel input is always isTrusted, so this only filters out
|
|
7230
7288
|
// the synthetic replay.
|
|
7231
7289
|
if (!event.isTrusted) return;
|
|
7290
|
+
// Pan/zoom mid-drag invalidates the gesture's cached snap viewport.
|
|
7291
|
+
invalidateSnapGesture();
|
|
7232
7292
|
event.preventDefault();
|
|
7233
7293
|
event.stopPropagation();
|
|
7234
7294
|
enqueueWheelGestureFromClient({
|
|
@@ -8406,9 +8466,9 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
8406
8466
|
beginRotate(singleSelectedFrame.id, event)
|
|
8407
8467
|
}
|
|
8408
8468
|
onStartDrag={
|
|
8409
|
-
// A running app's frame is dragged by its label
|
|
8410
|
-
//
|
|
8411
|
-
//
|
|
8469
|
+
// A running app's frame is dragged by its label: its content
|
|
8470
|
+
// wants the raw click, not a layer selection, so the drag
|
|
8471
|
+
// surface must never cover it.
|
|
8412
8472
|
singleSelectedFrameIsRunningApp
|
|
8413
8473
|
? undefined
|
|
8414
8474
|
: (event) => beginFrameDrag(singleSelectedFrame.id, event)
|
|
@@ -8460,7 +8520,8 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
8460
8520
|
{alignmentGuides.map((guide, index) => (
|
|
8461
8521
|
<span
|
|
8462
8522
|
key={`${guide.orientation}-${guide.position}-${index}`}
|
|
8463
|
-
|
|
8523
|
+
data-canvas-guide="alignment"
|
|
8524
|
+
className="pointer-events-none absolute z-30 bg-[var(--design-editor-measure-color)]"
|
|
8464
8525
|
style={
|
|
8465
8526
|
guide.orientation === "vertical"
|
|
8466
8527
|
? {
|
|
@@ -8479,33 +8540,26 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
8479
8540
|
/>
|
|
8480
8541
|
))}
|
|
8481
8542
|
|
|
8482
|
-
{/* Smart-spacing guides (CV11): highlight both equal-sized gaps
|
|
8483
|
-
around the moving frame, with one label showing the shared
|
|
8484
|
-
distance. */}
|
|
8485
8543
|
{equalGapGuides.map((guide, index) =>
|
|
8486
8544
|
guide.bands.map((band, bandIndex) => (
|
|
8487
|
-
<
|
|
8545
|
+
<SpacingGuideMark
|
|
8488
8546
|
key={`equal-gap-${guide.orientation}-${index}-${bandIndex}`}
|
|
8489
|
-
|
|
8490
|
-
|
|
8491
|
-
|
|
8492
|
-
? {
|
|
8493
|
-
left: SURFACE_PADDING + band.gapStart,
|
|
8494
|
-
top: SURFACE_PADDING + band.crossStart,
|
|
8495
|
-
width: Math.max(1, band.gapEnd - band.gapStart),
|
|
8496
|
-
height: Math.max(1, band.crossEnd - band.crossStart),
|
|
8497
|
-
}
|
|
8498
|
-
: {
|
|
8499
|
-
left: SURFACE_PADDING + band.crossStart,
|
|
8500
|
-
top: SURFACE_PADDING + band.gapStart,
|
|
8501
|
-
width: Math.max(1, band.crossEnd - band.crossStart),
|
|
8502
|
-
height: Math.max(1, band.gapEnd - band.gapStart),
|
|
8503
|
-
}
|
|
8504
|
-
}
|
|
8547
|
+
band={band}
|
|
8548
|
+
orientation={guide.orientation}
|
|
8549
|
+
chromeScale={chromeScale}
|
|
8505
8550
|
/>
|
|
8506
8551
|
)),
|
|
8507
8552
|
)}
|
|
8508
8553
|
|
|
8554
|
+
{proximityMeasurements.map((measurement) => (
|
|
8555
|
+
<SpacingGuideMark
|
|
8556
|
+
key={`proximity-${measurement.orientation}`}
|
|
8557
|
+
band={measurement.band}
|
|
8558
|
+
orientation={measurement.orientation}
|
|
8559
|
+
chromeScale={chromeScale}
|
|
8560
|
+
/>
|
|
8561
|
+
))}
|
|
8562
|
+
|
|
8509
8563
|
{/* Figma-parity alt-hover measurement: orange edge-to-edge distance
|
|
8510
8564
|
lines between the current selection and whatever frame/draft is
|
|
8511
8565
|
under the cursor while Alt is held (pure hover, no drag). */}
|
|
@@ -8610,7 +8664,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
8610
8664
|
return (
|
|
8611
8665
|
<span
|
|
8612
8666
|
key={`equal-gap-label-${guide.orientation}-${index}`}
|
|
8613
|
-
className="pointer-events-none absolute z-40 -translate-x-1/2 -translate-y-1/2 rounded bg-[var(--design-editor-
|
|
8667
|
+
className="pointer-events-none absolute z-40 -translate-x-1/2 -translate-y-1/2 rounded bg-[var(--design-editor-measure-color)] px-1 py-0.5 text-[10px] font-medium leading-none text-[var(--design-editor-accent-contrast-color)] shadow-sm"
|
|
8614
8668
|
style={{
|
|
8615
8669
|
left: pan.x + (SURFACE_PADDING + labelCanvasPoint.x) * scale,
|
|
8616
8670
|
top: pan.y + (SURFACE_PADDING + labelCanvasPoint.y) * scale,
|
|
@@ -8621,6 +8675,28 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
8621
8675
|
);
|
|
8622
8676
|
})}
|
|
8623
8677
|
|
|
8678
|
+
{proximityMeasurements.map((measurement) => {
|
|
8679
|
+
const { band } = measurement;
|
|
8680
|
+
const crossMid = (band.crossStart + band.crossEnd) / 2;
|
|
8681
|
+
const gapMid = (band.gapStart + band.gapEnd) / 2;
|
|
8682
|
+
const point =
|
|
8683
|
+
measurement.orientation === "vertical"
|
|
8684
|
+
? { x: gapMid, y: crossMid }
|
|
8685
|
+
: { x: crossMid, y: gapMid };
|
|
8686
|
+
return (
|
|
8687
|
+
<span
|
|
8688
|
+
key={`proximity-label-${measurement.orientation}`}
|
|
8689
|
+
className="pointer-events-none absolute z-40 -translate-x-1/2 -translate-y-1/2 rounded bg-[var(--design-editor-measure-color)] px-1 py-0.5 text-[10px] font-medium leading-none text-[var(--design-editor-accent-contrast-color)] shadow-sm"
|
|
8690
|
+
style={{
|
|
8691
|
+
left: pan.x + (SURFACE_PADDING + point.x) * scale,
|
|
8692
|
+
top: pan.y + (SURFACE_PADDING + point.y) * scale,
|
|
8693
|
+
}}
|
|
8694
|
+
>
|
|
8695
|
+
{Math.round(measurement.gap)}
|
|
8696
|
+
</span>
|
|
8697
|
+
);
|
|
8698
|
+
})}
|
|
8699
|
+
|
|
8624
8700
|
{penActive || creationToolActive ? (
|
|
8625
8701
|
<div
|
|
8626
8702
|
data-canvas-creation-shield
|
|
@@ -10850,6 +10926,78 @@ function BreakpointPreviewRow({
|
|
|
10850
10926
|
);
|
|
10851
10927
|
}
|
|
10852
10928
|
|
|
10929
|
+
/** Figma's spacing indicator: a thin line down the middle of the gap with a
|
|
10930
|
+
* serif at each end, drawn in the same red as the alignment guides. Lives in
|
|
10931
|
+
* the pan/zoom-transformed world, so every screen-constant dimension is
|
|
10932
|
+
* multiplied by `chromeScale`. */
|
|
10933
|
+
function SpacingGuideMark({
|
|
10934
|
+
band,
|
|
10935
|
+
orientation,
|
|
10936
|
+
chromeScale,
|
|
10937
|
+
}: {
|
|
10938
|
+
band: DistanceGuideBand;
|
|
10939
|
+
orientation: EqualGapGuide["orientation"];
|
|
10940
|
+
chromeScale: number;
|
|
10941
|
+
}) {
|
|
10942
|
+
const line = chromeScale;
|
|
10943
|
+
const serif = 5 * chromeScale;
|
|
10944
|
+
const crossMid = (band.crossStart + band.crossEnd) / 2;
|
|
10945
|
+
const length = Math.max(0, band.gapEnd - band.gapStart);
|
|
10946
|
+
const alongAxis = orientation === "vertical";
|
|
10947
|
+
|
|
10948
|
+
return (
|
|
10949
|
+
<span
|
|
10950
|
+
data-canvas-guide="spacing"
|
|
10951
|
+
className="pointer-events-none absolute z-30"
|
|
10952
|
+
style={
|
|
10953
|
+
alongAxis
|
|
10954
|
+
? {
|
|
10955
|
+
left: SURFACE_PADDING + band.gapStart,
|
|
10956
|
+
top: SURFACE_PADDING + crossMid - serif,
|
|
10957
|
+
width: length,
|
|
10958
|
+
height: serif * 2,
|
|
10959
|
+
}
|
|
10960
|
+
: {
|
|
10961
|
+
left: SURFACE_PADDING + crossMid - serif,
|
|
10962
|
+
top: SURFACE_PADDING + band.gapStart,
|
|
10963
|
+
width: serif * 2,
|
|
10964
|
+
height: length,
|
|
10965
|
+
}
|
|
10966
|
+
}
|
|
10967
|
+
>
|
|
10968
|
+
<span
|
|
10969
|
+
className="absolute bg-[var(--design-editor-measure-color)]"
|
|
10970
|
+
style={
|
|
10971
|
+
alongAxis
|
|
10972
|
+
? { left: 0, top: serif - line / 2, width: length, height: line }
|
|
10973
|
+
: { left: serif - line / 2, top: 0, width: line, height: length }
|
|
10974
|
+
}
|
|
10975
|
+
/>
|
|
10976
|
+
{[0, length].map((offset, index) => (
|
|
10977
|
+
<span
|
|
10978
|
+
key={index}
|
|
10979
|
+
className="absolute bg-[var(--design-editor-measure-color)]"
|
|
10980
|
+
style={
|
|
10981
|
+
alongAxis
|
|
10982
|
+
? {
|
|
10983
|
+
left: offset - line / 2,
|
|
10984
|
+
top: 0,
|
|
10985
|
+
width: line,
|
|
10986
|
+
height: serif * 2,
|
|
10987
|
+
}
|
|
10988
|
+
: {
|
|
10989
|
+
left: 0,
|
|
10990
|
+
top: offset - line / 2,
|
|
10991
|
+
width: serif * 2,
|
|
10992
|
+
height: line,
|
|
10993
|
+
}
|
|
10994
|
+
}
|
|
10995
|
+
/>
|
|
10996
|
+
))}
|
|
10997
|
+
</span>
|
|
10998
|
+
);
|
|
10999
|
+
}
|
|
11000
|
+
|
|
10853
11001
|
function GroupSelectionBox({
|
|
10854
11002
|
bounds,
|
|
10855
11003
|
chromeScale,
|