@agent-native/core 0.187.0 → 0.187.1-nightly-20260925003003
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/corpus/README.md +1 -1
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +4 -3
- package/corpus/templates/analytics/changelog/2026-09-24-analytics-sidebar-footer-aligns-consistently.md +6 -0
- package/corpus/templates/analytics/changelog/2026-09-24-dashboard-emails-match-stacked-area-totals.md +6 -0
- package/corpus/templates/analytics/server/lib/report-chart-svg.ts +47 -20
- package/corpus/templates/calendar/app/components/calendar/AttendeeAutocomplete.tsx +1 -1
- package/corpus/templates/calendar/changelog/2026-09-24-guest-inputs-wrap-beneath-existing-guests-so-the-active-inpu.md +6 -0
- package/corpus/templates/clips/app/components/editor/stitch-manager.tsx +8 -3
- package/corpus/templates/clips/app/lib/recording-link.ts +21 -0
- package/corpus/templates/clips/app/routes/record.tsx +27 -8
- package/corpus/templates/clips/changelog/2026-09-24-the-camera-bubble-stays-visible-while-you-reposition-it-befo.md +6 -0
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +7 -0
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +63 -55
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +251 -74
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +66 -56
- package/corpus/templates/design/app/components/design/EditPanel.tsx +19 -0
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +59 -9
- package/corpus/templates/design/app/components/design/edit-panel/interaction-state-helpers.ts +4 -0
- package/corpus/templates/design/app/components/design/edit-panel/layout-properties.tsx +119 -0
- package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +48 -51
- package/corpus/templates/design/app/components/design/edit-panel/style-change-types.ts +1 -0
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +298 -187
- package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +2 -2
- package/corpus/templates/design/app/components/design/inspector/index.ts +4 -0
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +4 -2
- package/corpus/templates/design/app/i18n/en-US.ts +6 -0
- package/corpus/templates/design/app/pages/Present.tsx +101 -87
- package/corpus/templates/design/app/pages/design-editor/clone-and-pen-edit.ts +75 -14
- package/corpus/templates/design/app/pages/design-editor/commands/README.md +1 -0
- package/corpus/templates/design/app/pages/design-editor/commands/commit-relative-style-delta-to-selected-layers.ts +69 -56
- package/corpus/templates/design/app/pages/design-editor/commands/mode-change.ts +16 -0
- package/corpus/templates/design/app/pages/design-editor/commands/scale-selection.ts +17 -2
- package/corpus/templates/design/app/pages/design-editor/commands/styles-change.ts +23 -30
- package/corpus/templates/design/app/pages/design-editor/commands/swap-fill-stroke.ts +105 -0
- package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +21 -0
- package/corpus/templates/design/app/pages/present-review-state.ts +7 -1
- package/corpus/templates/design/changelog/2026-09-24-a-vector-s-stroke-row-shows-position-beside-weight-instead-o.md +6 -0
- package/corpus/templates/design/changelog/2026-09-24-css-margins-can-be-adjusted-on-the-canvas-and-in-the-layout-.md +6 -0
- package/corpus/templates/design/changelog/2026-09-24-design-reviews-show-the-real-canvas-beside-its-conversation.md +6 -0
- package/corpus/templates/design/changelog/2026-09-24-double-clicking-text-or-a-vector-on-the-board-starts-editing.md +6 -0
- package/corpus/templates/design/changelog/2026-09-24-interact-opens-a-focused-responsive-preview-without-reloadin.md +6 -0
- package/corpus/templates/design/changelog/2026-09-24-keep-gradient-values-visible-and-limit-canvas-paints-to-supp.md +6 -0
- package/corpus/templates/design/changelog/2026-09-24-open-pen-paths-no-longer-paint-a-filled-triangle-after-shift.md +6 -0
- package/corpus/templates/design/changelog/2026-09-24-re-clicking-the-active-responsive-preview-returns-to-the-can.md +6 -0
- package/corpus/templates/design/changelog/2026-09-24-resized-vectors-can-be-point-edited-and-the-k-scale-tool-sho.md +6 -0
- package/corpus/templates/design/changelog/2026-09-24-while-editing-text-click-to-place-the-caret-and-double-click.md +6 -0
- package/corpus/templates/design/e2e/helpers.ts +19 -2
- package/corpus/templates/mail/app/components/email/ComposeModal.tsx +1 -1
- package/corpus/templates/mail/changelog/2026-09-24-gmail-push-ack.md +6 -0
- package/corpus/templates/mail/changelog/2026-09-24-the-composer-keeps-its-taller-layout-in-a-narrower-window.md +6 -0
- package/corpus/templates/mail/server/db/schema.ts +26 -1
- package/corpus/templates/mail/server/lib/inbox-store.ts +68 -23
- package/corpus/templates/mail/server/lib/inbox-sync.ts +43 -17
- package/corpus/templates/mail/server/plugins/db.ts +19 -0
- package/corpus/templates/mail/server/routes/api/gmail/push.post.ts +4 -15
- package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +45 -30
- package/corpus/templates/slides/.agents/skills/slide-editing/SKILL.md +18 -2
- package/corpus/templates/slides/actions/export-html.ts +28 -17
- package/corpus/templates/slides/app/hooks/use-deck-design-system.ts +27 -15
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +1 -1
- package/corpus/templates/slides/changelog/2026-09-23-decks-with-no-design-system-now-follow-a-theme-chosen-for-th.md +6 -0
- package/corpus/templates/slides/shared/slide-background.ts +3 -1
- package/dist/agent/engine/builder-engine.js +11 -0
- package/dist/agent/engine/types.d.ts +1 -0
- package/dist/agent/production-agent.d.ts +2 -0
- package/dist/agent/production-agent.js +32 -4
- package/dist/agent/run-loop-with-resume.js +6 -0
- package/dist/cli/index.js +4 -4
- package/dist/cli/process.d.ts +10 -1
- package/dist/cli/process.js +66 -0
- package/dist/cli/workspace-dev.js +2 -0
- package/dist/client/agent-chat.js +13 -14
- package/dist/client/error-format.js +4 -4
- package/dist/client/observability/ObservabilityDashboard.js +95 -65
- package/dist/client/observability/OutputPreview.d.ts +2 -1
- package/dist/client/observability/OutputPreview.js +64 -7
- package/dist/client/observability/useObservability.d.ts +2 -1
- package/dist/client/observability/useObservability.js +5 -1
- package/dist/client/settings/UsageSection.js +105 -17
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/db/client.d.ts +44 -0
- package/dist/db/client.js +62 -3
- package/dist/db/create-get-db.js +23 -1
- package/dist/db/embedded-runtime.d.ts +21 -0
- package/dist/db/embedded-runtime.js +27 -0
- package/dist/db/migrations.js +2 -0
- package/dist/db/server-runtime.d.ts +22 -0
- package/dist/db/server-runtime.js +26 -0
- package/dist/deploy/build.d.ts +29 -0
- package/dist/deploy/build.js +204 -121
- package/dist/feature-flags/index.d.ts +1 -1
- package/dist/feature-flags/index.js +1 -1
- package/dist/feature-flags/registry.d.ts +1 -0
- package/dist/feature-flags/registry.js +5 -0
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/integrations/webhook-handler.js +4 -1
- package/dist/jobs/background-automation-runner.js +6 -1
- package/dist/localization/core-messages/ar-SA.js +9 -1
- package/dist/localization/core-messages/de-DE.js +9 -1
- package/dist/localization/core-messages/en-US.d.ts +9 -1
- package/dist/localization/core-messages/en-US.js +9 -1
- package/dist/localization/core-messages/es-ES.js +9 -1
- package/dist/localization/core-messages/fr-FR.js +9 -1
- package/dist/localization/core-messages/hi-IN.js +9 -1
- package/dist/localization/core-messages/ja-JP.js +9 -1
- package/dist/localization/core-messages/ko-KR.js +9 -1
- package/dist/localization/core-messages/pt-BR.js +9 -1
- package/dist/localization/core-messages/supplemental/ar-SA.js +1 -0
- package/dist/localization/core-messages/supplemental/de-DE.js +1 -0
- package/dist/localization/core-messages/supplemental/en-US.js +1 -0
- package/dist/localization/core-messages/supplemental/es-ES.js +1 -0
- package/dist/localization/core-messages/supplemental/fr-FR.js +1 -0
- package/dist/localization/core-messages/supplemental/hi-IN.js +1 -0
- package/dist/localization/core-messages/supplemental/ja-JP.js +1 -0
- package/dist/localization/core-messages/supplemental/ko-KR.js +1 -0
- package/dist/localization/core-messages/supplemental/pt-BR.js +1 -0
- package/dist/localization/core-messages/supplemental/zh-CN.js +1 -0
- package/dist/localization/core-messages/supplemental/zh-TW.js +1 -0
- package/dist/localization/core-messages/zh-CN.js +9 -1
- package/dist/localization/core-messages/zh-TW.js +9 -1
- package/dist/localization/core-messages.d.ts +9 -1
- package/dist/localization/mcp-settings-messages.d.ts +1 -0
- package/dist/mcp/connect-route.js +45 -8
- package/dist/mcp/connect-store.d.ts +7 -5
- package/dist/mcp/connect-store.js +30 -39
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/actions/get-observability-review-detail.d.ts +7 -0
- package/dist/observability/actions/get-observability-review-detail.js +25 -0
- package/dist/observability/reviews.d.ts +9 -1
- package/dist/observability/reviews.js +20 -0
- package/dist/observability/routes.d.ts +3 -3
- package/dist/observability/types.d.ts +8 -0
- package/dist/org/handlers.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
- package/dist/provider-api/actions/provider-api.d.ts +9 -9
- package/dist/scripts/db/query.js +6 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-engine-ollama-models-route.d.ts +2 -2
- package/dist/server/agent-teams.js +4 -1
- package/dist/server/auth.js +60 -20
- package/dist/server/better-auth-instance.js +7 -1
- package/dist/server/core-routes-plugin.d.ts +1 -1
- package/dist/server/core-routes-plugin.js +2 -0
- package/dist/server/embedded.js +6 -0
- package/dist/server/framework-request-handler.js +6 -0
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/usage/actions/get-usage-metrics.js +4 -1
- package/dist/usage/metrics-store.d.ts +19 -0
- package/dist/usage/metrics-store.js +106 -14
- package/dist/usage/store.d.ts +7 -2
- package/dist/usage/store.js +25 -4
- package/dist/vite/action-types-plugin.js +4 -0
- package/dist/vite/client.d.ts +6 -1
- package/dist/vite/client.js +40 -1
- package/package.json +4 -3
|
@@ -67,6 +67,7 @@ export default function Present() {
|
|
|
67
67
|
const location = useLocation();
|
|
68
68
|
const navigate = useNavigate();
|
|
69
69
|
const [searchParams] = useSearchParams();
|
|
70
|
+
const reviewEmbed = searchParams.get("reviewEmbed") === "1";
|
|
70
71
|
const { session } = useSession();
|
|
71
72
|
const [currentPage, setCurrentPage] = useState(0);
|
|
72
73
|
const [commentMode, setCommentMode] = useState(false);
|
|
@@ -107,7 +108,7 @@ export default function Present() {
|
|
|
107
108
|
newestFirst: true,
|
|
108
109
|
limit: 500,
|
|
109
110
|
},
|
|
110
|
-
{ enabled: Boolean(id) },
|
|
111
|
+
{ enabled: Boolean(id) && !reviewEmbed },
|
|
111
112
|
);
|
|
112
113
|
const canPost = Boolean(
|
|
113
114
|
session?.email &&
|
|
@@ -164,6 +165,7 @@ export default function Present() {
|
|
|
164
165
|
);
|
|
165
166
|
|
|
166
167
|
useEffect(() => {
|
|
168
|
+
if (reviewEmbed) return;
|
|
167
169
|
const commentId =
|
|
168
170
|
reviewThreadIdFromHash(location.hash) ?? searchParams.get("comment");
|
|
169
171
|
const comments = reviewQuery.data?.comments ?? [];
|
|
@@ -191,6 +193,7 @@ export default function Present() {
|
|
|
191
193
|
handleReviewThreadSelect,
|
|
192
194
|
reviewQuery.data?.comments,
|
|
193
195
|
location.hash,
|
|
196
|
+
reviewEmbed,
|
|
194
197
|
searchParams,
|
|
195
198
|
]);
|
|
196
199
|
|
|
@@ -198,10 +201,13 @@ export default function Present() {
|
|
|
198
201
|
const handleKeyDown = useCallback(
|
|
199
202
|
(e: KeyboardEvent) => {
|
|
200
203
|
if (e.key === "Escape") {
|
|
201
|
-
const action = resolvePresentEscapeAction(
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
204
|
+
const action = resolvePresentEscapeAction(
|
|
205
|
+
{
|
|
206
|
+
commentsOpen,
|
|
207
|
+
commentMode,
|
|
208
|
+
},
|
|
209
|
+
reviewEmbed,
|
|
210
|
+
);
|
|
205
211
|
if (action === "close-comments") setCommentsOpen(false);
|
|
206
212
|
if (action === "exit-presentation") void navigate(`/design/${id}`);
|
|
207
213
|
// ReviewCanvasPins owns "defer-to-comment-mode" so it can dismiss an
|
|
@@ -222,7 +228,7 @@ export default function Present() {
|
|
|
222
228
|
setCurrentPage((p) => Math.max(p - 1, 0));
|
|
223
229
|
}
|
|
224
230
|
},
|
|
225
|
-
[commentMode, commentsOpen, files.length, id, navigate],
|
|
231
|
+
[commentMode, commentsOpen, files.length, id, navigate, reviewEmbed],
|
|
226
232
|
);
|
|
227
233
|
|
|
228
234
|
useEffect(() => {
|
|
@@ -276,88 +282,94 @@ export default function Present() {
|
|
|
276
282
|
className="h-full w-full border-0"
|
|
277
283
|
title={`${design.title} — ${activeFile.filename}`}
|
|
278
284
|
/>
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
<div className="fixed right-4 top-4 z-[70] flex items-center gap-2">
|
|
294
|
-
<ReviewStatusBadge
|
|
295
|
-
status={reviewQuery.data?.reviewStatus?.status ?? "draft"}
|
|
296
|
-
/>
|
|
297
|
-
<Button
|
|
298
|
-
type="button"
|
|
299
|
-
variant="secondary"
|
|
300
|
-
size="sm"
|
|
301
|
-
className="gap-1.5 rounded-full bg-black/75 text-white shadow-lg hover:bg-black"
|
|
302
|
-
onClick={() => {
|
|
303
|
-
setCommentMode(false);
|
|
304
|
-
setCommentsOpen(true);
|
|
305
|
-
}}
|
|
306
|
-
>
|
|
307
|
-
<IconMessageCircle className="size-4" />
|
|
308
|
-
{t("review.presentComments")}
|
|
309
|
-
{reviewCommentCount > 0 ? ` · ${reviewCommentCount}` : ""}
|
|
310
|
-
</Button>
|
|
285
|
+
{!reviewEmbed ? (
|
|
286
|
+
<ReviewCanvasPins
|
|
287
|
+
active={commentMode}
|
|
288
|
+
onClose={() => setCommentMode(false)}
|
|
289
|
+
canvasSelector=".present-review-canvas"
|
|
290
|
+
resourceType="design"
|
|
291
|
+
resourceId={id}
|
|
292
|
+
targetId={activeFile.id}
|
|
293
|
+
canPost={canPost}
|
|
294
|
+
canResolve={canResolve}
|
|
295
|
+
currentUserEmail={session?.email}
|
|
296
|
+
focusRequest={reviewFocusRequest}
|
|
297
|
+
/>
|
|
298
|
+
) : null}
|
|
311
299
|
</div>
|
|
312
300
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
<
|
|
301
|
+
{!reviewEmbed ? (
|
|
302
|
+
<>
|
|
303
|
+
<div className="fixed right-4 top-4 z-[70] flex items-center gap-2">
|
|
304
|
+
<ReviewStatusBadge
|
|
305
|
+
status={reviewQuery.data?.reviewStatus?.status ?? "draft"}
|
|
306
|
+
/>
|
|
307
|
+
<Button
|
|
308
|
+
type="button"
|
|
309
|
+
variant="secondary"
|
|
310
|
+
size="sm"
|
|
311
|
+
className="gap-1.5 rounded-full bg-foreground/75 text-background shadow-lg hover:bg-foreground"
|
|
312
|
+
onClick={() => {
|
|
313
|
+
setCommentMode(false);
|
|
314
|
+
setCommentsOpen(true);
|
|
315
|
+
}}
|
|
316
|
+
>
|
|
320
317
|
<IconMessageCircle className="size-4" />
|
|
321
318
|
{t("review.presentComments")}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
319
|
+
{reviewCommentCount > 0 ? ` · ${reviewCommentCount}` : ""}
|
|
320
|
+
</Button>
|
|
321
|
+
</div>
|
|
322
|
+
|
|
323
|
+
<Sheet open={commentsOpen} onOpenChange={setCommentsOpen}>
|
|
324
|
+
<SheetContent
|
|
325
|
+
side="right"
|
|
326
|
+
className="flex w-[min(92vw,380px)] flex-col overflow-hidden p-0"
|
|
327
|
+
>
|
|
328
|
+
<SheetHeader className="border-b border-border px-4 py-3">
|
|
329
|
+
<SheetTitle className="flex items-center gap-2 text-sm">
|
|
330
|
+
<IconMessageCircle className="size-4" />
|
|
331
|
+
{t("review.presentComments")}
|
|
332
|
+
</SheetTitle>
|
|
333
|
+
<SheetDescription className="sr-only">
|
|
334
|
+
{t("review.commentsTitle")}
|
|
335
|
+
</SheetDescription>
|
|
336
|
+
</SheetHeader>
|
|
337
|
+
<ReviewCommentsPanel
|
|
338
|
+
designId={id}
|
|
339
|
+
canComment={canPost}
|
|
340
|
+
canResolve={canResolve}
|
|
341
|
+
currentTargetId={activeFile?.id ?? null}
|
|
342
|
+
currentUserEmail={session?.email}
|
|
343
|
+
canDeleteComment={(comment) =>
|
|
344
|
+
canResolve ||
|
|
345
|
+
("canDelete" in comment && comment.canDelete === true) ||
|
|
346
|
+
comment.authorEmail === session?.email
|
|
347
|
+
}
|
|
348
|
+
signInHref={signInHref}
|
|
349
|
+
onSelectThread={handleReviewThreadSelect}
|
|
350
|
+
className="min-h-0 flex-1"
|
|
351
|
+
/>
|
|
352
|
+
{canPost ? (
|
|
353
|
+
<div className="shrink-0 border-t border-border p-3">
|
|
354
|
+
<Button
|
|
355
|
+
type="button"
|
|
356
|
+
variant="outline"
|
|
357
|
+
size="sm"
|
|
358
|
+
className="w-full gap-1.5"
|
|
359
|
+
onClick={() => {
|
|
360
|
+
setCommentsOpen(false);
|
|
361
|
+
setCommentMode(true);
|
|
362
|
+
}}
|
|
363
|
+
>
|
|
364
|
+
<IconMessageCircle className="size-3.5" />
|
|
365
|
+
{t("review.presentCommentMode")}
|
|
366
|
+
</Button>
|
|
367
|
+
</div>
|
|
368
|
+
) : null}
|
|
369
|
+
</SheetContent>
|
|
370
|
+
</Sheet>
|
|
371
|
+
</>
|
|
372
|
+
) : null}
|
|
361
373
|
|
|
362
374
|
{/* Page indicator */}
|
|
363
375
|
{files.length > 1 && (
|
|
@@ -375,9 +387,11 @@ export default function Present() {
|
|
|
375
387
|
)}
|
|
376
388
|
|
|
377
389
|
{/* Exit hint */}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
390
|
+
{!reviewEmbed ? (
|
|
391
|
+
<div className="fixed left-4 top-4 text-xs text-foreground/20">
|
|
392
|
+
{t("pages.presentExitHint")}
|
|
393
|
+
</div>
|
|
394
|
+
) : null}
|
|
381
395
|
</div>
|
|
382
396
|
);
|
|
383
397
|
}
|
|
@@ -25,7 +25,9 @@ import {
|
|
|
25
25
|
penCornerRadiusFromAttribute,
|
|
26
26
|
serializePenPath,
|
|
27
27
|
serializePenNodes,
|
|
28
|
+
scalePenPathToGeometry,
|
|
28
29
|
serializeRoundedPenPath,
|
|
30
|
+
translatePenPath,
|
|
29
31
|
type PenPath,
|
|
30
32
|
} from "@shared/pen-path";
|
|
31
33
|
|
|
@@ -259,6 +261,33 @@ export function writeBackVectorEditedPenPath(
|
|
|
259
261
|
}
|
|
260
262
|
}
|
|
261
263
|
|
|
264
|
+
/** Axis-aligned scale plus the authored-to-screen-content translation. */
|
|
265
|
+
function penPathScreenContentMapping(svg: SVGSVGElement): {
|
|
266
|
+
scaleX: number;
|
|
267
|
+
scaleY: number;
|
|
268
|
+
offset: { x: number; y: number };
|
|
269
|
+
viewBox: { x: number; y: number; width: number; height: number };
|
|
270
|
+
} | null {
|
|
271
|
+
const viewBox = parseViewBox(svg.getAttribute("viewBox"));
|
|
272
|
+
const matrix = svg.getScreenCTM();
|
|
273
|
+
if (!viewBox || !matrix) return null;
|
|
274
|
+
if (
|
|
275
|
+
Math.abs(matrix.b) > 0.001 ||
|
|
276
|
+
Math.abs(matrix.c) > 0.001 ||
|
|
277
|
+
!(matrix.a > 0) ||
|
|
278
|
+
!(matrix.d > 0)
|
|
279
|
+
) {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
const view = svg.ownerDocument.defaultView;
|
|
283
|
+
const offset = {
|
|
284
|
+
x: matrix.a * viewBox.x + matrix.e + (view?.scrollX ?? 0) - viewBox.x,
|
|
285
|
+
y: matrix.d * viewBox.y + matrix.f + (view?.scrollY ?? 0) - viewBox.y,
|
|
286
|
+
};
|
|
287
|
+
if (!Number.isFinite(offset.x) || !Number.isFinite(offset.y)) return null;
|
|
288
|
+
return { scaleX: matrix.a, scaleY: matrix.d, offset, viewBox };
|
|
289
|
+
}
|
|
290
|
+
|
|
262
291
|
/**
|
|
263
292
|
* Returns the translation between an SVG PenPath's authored coordinates and
|
|
264
293
|
* its current screen-content position. Vector edit handles use screen-content
|
|
@@ -273,25 +302,57 @@ export function penPathScreenContentOffset(svg: SVGSVGElement): {
|
|
|
273
302
|
x: number;
|
|
274
303
|
y: number;
|
|
275
304
|
} | null {
|
|
276
|
-
const
|
|
277
|
-
const matrix = svg.getScreenCTM();
|
|
278
|
-
if (!viewBox || !matrix) return null;
|
|
305
|
+
const mapping = penPathScreenContentMapping(svg);
|
|
279
306
|
if (
|
|
280
|
-
|
|
281
|
-
Math.abs(
|
|
282
|
-
Math.abs(
|
|
283
|
-
Math.abs(matrix.d - 1) > 0.001
|
|
307
|
+
!mapping ||
|
|
308
|
+
Math.abs(mapping.scaleX - 1) > 0.001 ||
|
|
309
|
+
Math.abs(mapping.scaleY - 1) > 0.001
|
|
284
310
|
) {
|
|
285
311
|
return null;
|
|
286
312
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
313
|
+
return mapping.offset;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* A drawn vector's nodes in screen-content space for vector edit. A resize or
|
|
318
|
+
* K-scale leaves the viewBox behind, so the SVG draws at a scale; scaling the
|
|
319
|
+
* nodes about the viewBox origin lets writeBackVectorEditedPenPath re-base the
|
|
320
|
+
* SVG at 1:1 on commit, where `sourceOffset` maps them back.
|
|
321
|
+
*/
|
|
322
|
+
export function penPathForVectorEdit(
|
|
323
|
+
svg: SVGSVGElement,
|
|
324
|
+
path: PenPath,
|
|
325
|
+
renderOffset: { x: number; y: number },
|
|
326
|
+
): { path: PenPath; sourceOffset: { x: number; y: number } } | null {
|
|
327
|
+
const mapping = penPathScreenContentMapping(svg);
|
|
328
|
+
if (!mapping) return null;
|
|
329
|
+
const { viewBox, scaleX, scaleY } = mapping;
|
|
330
|
+
// Only the box-vs-viewBox stretch is re-based; a CSS transform survives
|
|
331
|
+
// write-back, so baking its scale into the nodes would apply it twice.
|
|
332
|
+
const boxStretchMatches = (scale: number, box: string, extent: number) => {
|
|
333
|
+
const size = parsePixelValue(box);
|
|
334
|
+
if (size === null) return false;
|
|
335
|
+
return extent === 0 || Math.abs(scale - size / extent) <= 0.001;
|
|
336
|
+
};
|
|
337
|
+
if (
|
|
338
|
+
!boxStretchMatches(scaleX, svg.style.width, viewBox.width) ||
|
|
339
|
+
!boxStretchMatches(scaleY, svg.style.height, viewBox.height)
|
|
340
|
+
) {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
const sourceOffset = {
|
|
344
|
+
x: mapping.offset.x - renderOffset.x,
|
|
345
|
+
y: mapping.offset.y - renderOffset.y,
|
|
346
|
+
};
|
|
347
|
+
const unscaled = scalePenPathToGeometry(path, viewBox, {
|
|
348
|
+
...viewBox,
|
|
349
|
+
width: viewBox.width * scaleX,
|
|
350
|
+
height: viewBox.height * scaleY,
|
|
351
|
+
});
|
|
352
|
+
return {
|
|
353
|
+
path: translatePenPath(unscaled, sourceOffset.x, sourceOffset.y),
|
|
354
|
+
sourceOffset,
|
|
293
355
|
};
|
|
294
|
-
return Number.isFinite(offset.x) && Number.isFinite(offset.y) ? offset : null;
|
|
295
356
|
}
|
|
296
357
|
|
|
297
358
|
/**
|
|
@@ -67,6 +67,7 @@ a module breaks those — re-point the spec in the same commit.
|
|
|
67
67
|
| `screen-visual-style-change.ts` | Canvas style change addressed by explicit `screenId` |
|
|
68
68
|
| `record-pending-visual-style-edit.ts` | Record an uncommitted style gesture, stamped with breakpoint scope |
|
|
69
69
|
| `change-selected-z-index.ts` | Raise or lower the selection's z-order |
|
|
70
|
+
| `swap-fill-stroke.ts` | Shift+X: swap fill and stroke, folding paint opacity into the colours |
|
|
70
71
|
|
|
71
72
|
## Text
|
|
72
73
|
|
|
@@ -128,7 +128,7 @@ export function runCommitRelativeStyleDeltaToSelectedLayers(
|
|
|
128
128
|
reportLinkedEditUnavailable,
|
|
129
129
|
setSelectedElement,
|
|
130
130
|
}: CommitRelativeStyleDeltaToSelectedLayersArgs,
|
|
131
|
-
property: string,
|
|
131
|
+
property: string | string[],
|
|
132
132
|
operation: number | ScrubRelativeExpression,
|
|
133
133
|
pendingUndoGestureId?: string,
|
|
134
134
|
) {
|
|
@@ -142,8 +142,37 @@ export function runCommitRelativeStyleDeltaToSelectedLayers(
|
|
|
142
142
|
!effectiveLayerState.hiddenIds.has(target.layerId),
|
|
143
143
|
);
|
|
144
144
|
if (targets.length === 0) return false;
|
|
145
|
+
const properties = [
|
|
146
|
+
...new Set(Array.isArray(property) ? property : [property]),
|
|
147
|
+
];
|
|
148
|
+
if (properties.length === 0) return false;
|
|
149
|
+
const relativeStylesByTarget = new Map<
|
|
150
|
+
SelectedLayerTarget,
|
|
151
|
+
Record<string, string>
|
|
152
|
+
>();
|
|
153
|
+
const stylesForTarget = (target: SelectedLayerTarget) => {
|
|
154
|
+
const styles: Record<string, string> = {};
|
|
155
|
+
for (const property of properties) {
|
|
156
|
+
const writeProperty = property === "rotation" ? "transform" : property;
|
|
157
|
+
const currentValue =
|
|
158
|
+
property === "rotation"
|
|
159
|
+
? (target.elementInfo.inlineStyles?.transform ??
|
|
160
|
+
target.elementInfo.computedStyles.transform)
|
|
161
|
+
: (target.elementInfo.computedStyles[
|
|
162
|
+
property as keyof typeof target.elementInfo.computedStyles
|
|
163
|
+
] as string | undefined);
|
|
164
|
+
const nextValue =
|
|
165
|
+
property === "rotation"
|
|
166
|
+
? applyRelativeRotationToTransform(currentValue, operation)
|
|
167
|
+
: typeof operation === "number"
|
|
168
|
+
? applyRelativeDeltaToStyleValue(currentValue, operation)
|
|
169
|
+
: applyRelativeExpressionToStyleValue(currentValue, operation);
|
|
170
|
+
if (nextValue === null) return null;
|
|
171
|
+
styles[writeProperty] = nextValue;
|
|
172
|
+
}
|
|
173
|
+
return styles;
|
|
174
|
+
};
|
|
145
175
|
|
|
146
|
-
const writeProperty = property === "rotation" ? "transform" : property;
|
|
147
176
|
const styleTargets: Array<{
|
|
148
177
|
fileId: string;
|
|
149
178
|
nodeId: string;
|
|
@@ -180,29 +209,18 @@ export function runCommitRelativeStyleDeltaToSelectedLayers(
|
|
|
180
209
|
targetsResolved = false;
|
|
181
210
|
return;
|
|
182
211
|
}
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
? (target.elementInfo.inlineStyles?.transform ??
|
|
186
|
-
target.elementInfo.computedStyles.transform)
|
|
187
|
-
: (target.elementInfo.computedStyles[
|
|
188
|
-
property as keyof typeof target.elementInfo.computedStyles
|
|
189
|
-
] as string | undefined);
|
|
190
|
-
const nextValue =
|
|
191
|
-
property === "rotation"
|
|
192
|
-
? applyRelativeRotationToTransform(currentValue, operation)
|
|
193
|
-
: typeof operation === "number"
|
|
194
|
-
? applyRelativeDeltaToStyleValue(currentValue, operation)
|
|
195
|
-
: applyRelativeExpressionToStyleValue(currentValue, operation);
|
|
196
|
-
if (nextValue === null) {
|
|
212
|
+
const styles = stylesForTarget(target);
|
|
213
|
+
if (!styles) {
|
|
197
214
|
targetsResolved = false;
|
|
198
215
|
return;
|
|
199
216
|
}
|
|
217
|
+
relativeStylesByTarget.set(target, styles);
|
|
200
218
|
if (linkedComponentRootForNode(node, projection))
|
|
201
219
|
includesLinkedTarget = true;
|
|
202
220
|
styleTargets.push({
|
|
203
221
|
fileId: target.fileId,
|
|
204
222
|
nodeId: node.dataAttributes["data-agent-native-node-id"] ?? "",
|
|
205
|
-
styles
|
|
223
|
+
styles,
|
|
206
224
|
});
|
|
207
225
|
});
|
|
208
226
|
if (includesLinkedTarget) {
|
|
@@ -293,7 +311,7 @@ export function runCommitRelativeStyleDeltaToSelectedLayers(
|
|
|
293
311
|
});
|
|
294
312
|
|
|
295
313
|
let appliedAny = false;
|
|
296
|
-
const
|
|
314
|
+
const appliedStylesByLayerId = new Map<string, Record<string, string>>();
|
|
297
315
|
const acceptedNodeByLayerId = new Map<
|
|
298
316
|
string,
|
|
299
317
|
NonNullable<ReturnType<typeof mapAcceptedSelectionNode>>
|
|
@@ -312,20 +330,8 @@ export function runCommitRelativeStyleDeltaToSelectedLayers(
|
|
|
312
330
|
let nextContent = baseContent;
|
|
313
331
|
let projection = buildCodeLayerProjection(nextContent, { source });
|
|
314
332
|
fileTargets.forEach((target) => {
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
? (target.elementInfo.inlineStyles?.transform ??
|
|
318
|
-
target.elementInfo.computedStyles.transform)
|
|
319
|
-
: (target.elementInfo.computedStyles[
|
|
320
|
-
property as keyof typeof target.elementInfo.computedStyles
|
|
321
|
-
] as string | undefined);
|
|
322
|
-
const nextValue =
|
|
323
|
-
property === "rotation"
|
|
324
|
-
? applyRelativeRotationToTransform(currentValue, operation)
|
|
325
|
-
: typeof operation === "number"
|
|
326
|
-
? applyRelativeDeltaToStyleValue(currentValue, operation)
|
|
327
|
-
: applyRelativeExpressionToStyleValue(currentValue, operation);
|
|
328
|
-
if (nextValue === null) return;
|
|
333
|
+
const styles = relativeStylesByTarget.get(target);
|
|
334
|
+
if (!styles) return;
|
|
329
335
|
const sourceId = bridgeSourceIdForCodeLayerNode(target.node);
|
|
330
336
|
const selector = preferredCodeLayerSelector(target.node);
|
|
331
337
|
const node =
|
|
@@ -336,22 +342,28 @@ export function runCommitRelativeStyleDeltaToSelectedLayers(
|
|
|
336
342
|
if (!node) return;
|
|
337
343
|
// §6.4 — relative-delta commits (mixed-value arrow steps) route
|
|
338
344
|
// through the same breakpoint scoping as absolute commits.
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
345
|
+
let targetNextContent = nextContent;
|
|
346
|
+
let targetProjection = projection;
|
|
347
|
+
for (const [writeProperty, value] of Object.entries(styles)) {
|
|
348
|
+
const patch = applyScopedVisualStyleEdit({
|
|
349
|
+
content: targetNextContent,
|
|
350
|
+
target: { nodeId: node.id },
|
|
351
|
+
property: writeProperty,
|
|
352
|
+
value,
|
|
353
|
+
source,
|
|
354
|
+
upperBoundPx: activeBreakpointUpperBoundPx,
|
|
355
|
+
lowerBoundPx:
|
|
356
|
+
responsiveEditScopeRef.current === "only"
|
|
357
|
+
? activeBreakpointWidthStateRef.current
|
|
358
|
+
: null,
|
|
359
|
+
});
|
|
360
|
+
if (patch.result.status !== "applied") return;
|
|
361
|
+
targetNextContent = patch.content;
|
|
362
|
+
targetProjection = patch.projection;
|
|
363
|
+
}
|
|
364
|
+
nextContent = targetNextContent;
|
|
365
|
+
projection = targetProjection;
|
|
366
|
+
appliedStylesByLayerId.set(target.layerId, styles);
|
|
355
367
|
});
|
|
356
368
|
if (nextContent === baseContent) return;
|
|
357
369
|
appliedAny = true;
|
|
@@ -386,10 +398,10 @@ export function runCommitRelativeStyleDeltaToSelectedLayers(
|
|
|
386
398
|
|
|
387
399
|
if (appliedAny) {
|
|
388
400
|
const primaryTarget = targets[targets.length - 1];
|
|
389
|
-
const
|
|
390
|
-
?
|
|
401
|
+
const primaryStyles = primaryTarget
|
|
402
|
+
? appliedStylesByLayerId.get(primaryTarget.layerId)
|
|
391
403
|
: undefined;
|
|
392
|
-
if (primaryTarget &&
|
|
404
|
+
if (primaryTarget && primaryStyles !== undefined) {
|
|
393
405
|
const acceptedPrimaryTarget = acceptedNodeByLayerId.get(
|
|
394
406
|
primaryTarget.layerId,
|
|
395
407
|
);
|
|
@@ -412,14 +424,15 @@ export function runCommitRelativeStyleDeltaToSelectedLayers(
|
|
|
412
424
|
...base,
|
|
413
425
|
computedStyles: {
|
|
414
426
|
...base.computedStyles,
|
|
415
|
-
|
|
427
|
+
...primaryStyles,
|
|
416
428
|
},
|
|
417
|
-
inlineStyles: patchAuthoredInlineStyles(
|
|
418
|
-
|
|
419
|
-
|
|
429
|
+
inlineStyles: patchAuthoredInlineStyles(
|
|
430
|
+
base.inlineStyles,
|
|
431
|
+
primaryStyles,
|
|
432
|
+
),
|
|
420
433
|
authoredSizeStyles: clearAuthoredSizeStylesForCommit(
|
|
421
434
|
base.authoredSizeStyles,
|
|
422
|
-
|
|
435
|
+
primaryStyles,
|
|
423
436
|
),
|
|
424
437
|
};
|
|
425
438
|
});
|
|
@@ -34,6 +34,8 @@ export interface ModeChangeArgs {
|
|
|
34
34
|
setMode: Dispatch<SetStateAction<EditorMode>>;
|
|
35
35
|
setPinMode: Dispatch<SetStateAction<boolean>>;
|
|
36
36
|
setSelectedElement: Dispatch<SetStateAction<ElementInfo | null>>;
|
|
37
|
+
overviewInteractScreenId: string | null;
|
|
38
|
+
setOverviewInteractScreenId: Dispatch<SetStateAction<string | null>>;
|
|
37
39
|
t: (key: string, options?: Record<string, unknown>) => string;
|
|
38
40
|
viewModeRef: RefObject<"single" | "overview">;
|
|
39
41
|
}
|
|
@@ -56,6 +58,8 @@ export function runModeChange(
|
|
|
56
58
|
setMode,
|
|
57
59
|
setPinMode,
|
|
58
60
|
setSelectedElement,
|
|
61
|
+
overviewInteractScreenId,
|
|
62
|
+
setOverviewInteractScreenId,
|
|
59
63
|
t,
|
|
60
64
|
viewModeRef,
|
|
61
65
|
}: ModeChangeArgs,
|
|
@@ -94,14 +98,26 @@ export function runModeChange(
|
|
|
94
98
|
viewMode: viewModeRef.current,
|
|
95
99
|
});
|
|
96
100
|
if (routing === "enter-single-interact") {
|
|
101
|
+
setOverviewInteractScreenId(nextActiveFile!.id);
|
|
97
102
|
enterSingleScreen(nextActiveFile?.id);
|
|
98
103
|
return;
|
|
99
104
|
}
|
|
100
105
|
if (routing === "enter-overview") {
|
|
106
|
+
setOverviewInteractScreenId(null);
|
|
101
107
|
if (options?.targetFileId) setActiveFileId(options.targetFileId);
|
|
102
108
|
enterOverviewFromZoom(next);
|
|
103
109
|
return;
|
|
104
110
|
}
|
|
111
|
+
if (next === "interact" && overviewInteractScreenId) {
|
|
112
|
+
setOverviewInteractScreenId(nextActiveFile!.id);
|
|
113
|
+
if (
|
|
114
|
+
options?.targetFileId &&
|
|
115
|
+
nextActiveFile!.id !== overviewInteractScreenId
|
|
116
|
+
) {
|
|
117
|
+
enterSingleScreen(nextActiveFile!.id);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
105
121
|
if (options?.targetFileId) setActiveFileId(options.targetFileId);
|
|
106
122
|
setMode(next);
|
|
107
123
|
setSelectedElement(null);
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { ScaleAnchor } from "@/components/design/edit-panel/scale-properties";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
findCanvasIframeForScreen,
|
|
4
|
+
getBreakpointIframeId,
|
|
5
|
+
} from "@/components/design/multi-screen/iframe-targeting";
|
|
3
6
|
import type { ElementInfo } from "@/components/design/types";
|
|
4
7
|
|
|
5
8
|
/**
|
|
@@ -11,6 +14,7 @@ export function runScaleSelection(
|
|
|
11
14
|
args: {
|
|
12
15
|
selectedElement: ElementInfo | null;
|
|
13
16
|
boardFileId: string | null | undefined;
|
|
17
|
+
activeBreakpointWidthPx: number | undefined;
|
|
14
18
|
fallbackIframe: HTMLIFrameElement | null;
|
|
15
19
|
},
|
|
16
20
|
factor: number,
|
|
@@ -19,14 +23,25 @@ export function runScaleSelection(
|
|
|
19
23
|
const selector = args.selectedElement?.selector;
|
|
20
24
|
if (!selector) return;
|
|
21
25
|
const screenId = args.selectedElement?.sourceLayerIdentity?.screenId;
|
|
26
|
+
// While a breakpoint is being edited, its own frame holds the selection
|
|
27
|
+
// at that width; the screen's primary frame would scale the wrong size.
|
|
28
|
+
const breakpointIframe =
|
|
29
|
+
screenId && args.activeBreakpointWidthPx !== undefined
|
|
30
|
+
? findCanvasIframeForScreen(
|
|
31
|
+
document.body,
|
|
32
|
+
getBreakpointIframeId(screenId, args.activeBreakpointWidthPx),
|
|
33
|
+
)
|
|
34
|
+
: null;
|
|
22
35
|
const iframe =
|
|
36
|
+
breakpointIframe ??
|
|
23
37
|
(screenId
|
|
24
38
|
? findCanvasIframeForScreen(
|
|
25
39
|
document.body,
|
|
26
40
|
screenId,
|
|
27
41
|
args.boardFileId ?? undefined,
|
|
28
42
|
)
|
|
29
|
-
: null) ??
|
|
43
|
+
: null) ??
|
|
44
|
+
args.fallbackIframe;
|
|
30
45
|
iframe?.contentWindow?.postMessage(
|
|
31
46
|
{
|
|
32
47
|
type: "agent-native:scale-selection",
|