@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.
Files changed (158) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +4 -3
  3. package/corpus/templates/analytics/changelog/2026-09-24-analytics-sidebar-footer-aligns-consistently.md +6 -0
  4. package/corpus/templates/analytics/changelog/2026-09-24-dashboard-emails-match-stacked-area-totals.md +6 -0
  5. package/corpus/templates/analytics/server/lib/report-chart-svg.ts +47 -20
  6. package/corpus/templates/calendar/app/components/calendar/AttendeeAutocomplete.tsx +1 -1
  7. package/corpus/templates/calendar/changelog/2026-09-24-guest-inputs-wrap-beneath-existing-guests-so-the-active-inpu.md +6 -0
  8. package/corpus/templates/clips/app/components/editor/stitch-manager.tsx +8 -3
  9. package/corpus/templates/clips/app/lib/recording-link.ts +21 -0
  10. package/corpus/templates/clips/app/routes/record.tsx +27 -8
  11. package/corpus/templates/clips/changelog/2026-09-24-the-camera-bubble-stays-visible-while-you-reposition-it-befo.md +6 -0
  12. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +7 -0
  13. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +63 -55
  14. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +251 -74
  15. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +66 -56
  16. package/corpus/templates/design/app/components/design/EditPanel.tsx +19 -0
  17. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +59 -9
  18. package/corpus/templates/design/app/components/design/edit-panel/interaction-state-helpers.ts +4 -0
  19. package/corpus/templates/design/app/components/design/edit-panel/layout-properties.tsx +119 -0
  20. package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +48 -51
  21. package/corpus/templates/design/app/components/design/edit-panel/style-change-types.ts +1 -0
  22. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +298 -187
  23. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +2 -2
  24. package/corpus/templates/design/app/components/design/inspector/index.ts +4 -0
  25. package/corpus/templates/design/app/components/design/multi-screen/types.ts +4 -2
  26. package/corpus/templates/design/app/i18n/en-US.ts +6 -0
  27. package/corpus/templates/design/app/pages/Present.tsx +101 -87
  28. package/corpus/templates/design/app/pages/design-editor/clone-and-pen-edit.ts +75 -14
  29. package/corpus/templates/design/app/pages/design-editor/commands/README.md +1 -0
  30. package/corpus/templates/design/app/pages/design-editor/commands/commit-relative-style-delta-to-selected-layers.ts +69 -56
  31. package/corpus/templates/design/app/pages/design-editor/commands/mode-change.ts +16 -0
  32. package/corpus/templates/design/app/pages/design-editor/commands/scale-selection.ts +17 -2
  33. package/corpus/templates/design/app/pages/design-editor/commands/styles-change.ts +23 -30
  34. package/corpus/templates/design/app/pages/design-editor/commands/swap-fill-stroke.ts +105 -0
  35. package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +21 -0
  36. package/corpus/templates/design/app/pages/present-review-state.ts +7 -1
  37. package/corpus/templates/design/changelog/2026-09-24-a-vector-s-stroke-row-shows-position-beside-weight-instead-o.md +6 -0
  38. package/corpus/templates/design/changelog/2026-09-24-css-margins-can-be-adjusted-on-the-canvas-and-in-the-layout-.md +6 -0
  39. package/corpus/templates/design/changelog/2026-09-24-design-reviews-show-the-real-canvas-beside-its-conversation.md +6 -0
  40. package/corpus/templates/design/changelog/2026-09-24-double-clicking-text-or-a-vector-on-the-board-starts-editing.md +6 -0
  41. package/corpus/templates/design/changelog/2026-09-24-interact-opens-a-focused-responsive-preview-without-reloadin.md +6 -0
  42. package/corpus/templates/design/changelog/2026-09-24-keep-gradient-values-visible-and-limit-canvas-paints-to-supp.md +6 -0
  43. package/corpus/templates/design/changelog/2026-09-24-open-pen-paths-no-longer-paint-a-filled-triangle-after-shift.md +6 -0
  44. package/corpus/templates/design/changelog/2026-09-24-re-clicking-the-active-responsive-preview-returns-to-the-can.md +6 -0
  45. package/corpus/templates/design/changelog/2026-09-24-resized-vectors-can-be-point-edited-and-the-k-scale-tool-sho.md +6 -0
  46. package/corpus/templates/design/changelog/2026-09-24-while-editing-text-click-to-place-the-caret-and-double-click.md +6 -0
  47. package/corpus/templates/design/e2e/helpers.ts +19 -2
  48. package/corpus/templates/mail/app/components/email/ComposeModal.tsx +1 -1
  49. package/corpus/templates/mail/changelog/2026-09-24-gmail-push-ack.md +6 -0
  50. package/corpus/templates/mail/changelog/2026-09-24-the-composer-keeps-its-taller-layout-in-a-narrower-window.md +6 -0
  51. package/corpus/templates/mail/server/db/schema.ts +26 -1
  52. package/corpus/templates/mail/server/lib/inbox-store.ts +68 -23
  53. package/corpus/templates/mail/server/lib/inbox-sync.ts +43 -17
  54. package/corpus/templates/mail/server/plugins/db.ts +19 -0
  55. package/corpus/templates/mail/server/routes/api/gmail/push.post.ts +4 -15
  56. package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +45 -30
  57. package/corpus/templates/slides/.agents/skills/slide-editing/SKILL.md +18 -2
  58. package/corpus/templates/slides/actions/export-html.ts +28 -17
  59. package/corpus/templates/slides/app/hooks/use-deck-design-system.ts +27 -15
  60. package/corpus/templates/slides/app/pages/DeckEditor.tsx +1 -1
  61. package/corpus/templates/slides/changelog/2026-09-23-decks-with-no-design-system-now-follow-a-theme-chosen-for-th.md +6 -0
  62. package/corpus/templates/slides/shared/slide-background.ts +3 -1
  63. package/dist/agent/engine/builder-engine.js +11 -0
  64. package/dist/agent/engine/types.d.ts +1 -0
  65. package/dist/agent/production-agent.d.ts +2 -0
  66. package/dist/agent/production-agent.js +32 -4
  67. package/dist/agent/run-loop-with-resume.js +6 -0
  68. package/dist/cli/index.js +4 -4
  69. package/dist/cli/process.d.ts +10 -1
  70. package/dist/cli/process.js +66 -0
  71. package/dist/cli/workspace-dev.js +2 -0
  72. package/dist/client/agent-chat.js +13 -14
  73. package/dist/client/error-format.js +4 -4
  74. package/dist/client/observability/ObservabilityDashboard.js +95 -65
  75. package/dist/client/observability/OutputPreview.d.ts +2 -1
  76. package/dist/client/observability/OutputPreview.js +64 -7
  77. package/dist/client/observability/useObservability.d.ts +2 -1
  78. package/dist/client/observability/useObservability.js +5 -1
  79. package/dist/client/settings/UsageSection.js +105 -17
  80. package/dist/collab/routes.d.ts +1 -1
  81. package/dist/collab/struct-routes.d.ts +1 -1
  82. package/dist/db/client.d.ts +44 -0
  83. package/dist/db/client.js +62 -3
  84. package/dist/db/create-get-db.js +23 -1
  85. package/dist/db/embedded-runtime.d.ts +21 -0
  86. package/dist/db/embedded-runtime.js +27 -0
  87. package/dist/db/migrations.js +2 -0
  88. package/dist/db/server-runtime.d.ts +22 -0
  89. package/dist/db/server-runtime.js +26 -0
  90. package/dist/deploy/build.d.ts +29 -0
  91. package/dist/deploy/build.js +204 -121
  92. package/dist/feature-flags/index.d.ts +1 -1
  93. package/dist/feature-flags/index.js +1 -1
  94. package/dist/feature-flags/registry.d.ts +1 -0
  95. package/dist/feature-flags/registry.js +5 -0
  96. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  97. package/dist/index.d.ts +1 -1
  98. package/dist/index.js +1 -1
  99. package/dist/integrations/webhook-handler.js +4 -1
  100. package/dist/jobs/background-automation-runner.js +6 -1
  101. package/dist/localization/core-messages/ar-SA.js +9 -1
  102. package/dist/localization/core-messages/de-DE.js +9 -1
  103. package/dist/localization/core-messages/en-US.d.ts +9 -1
  104. package/dist/localization/core-messages/en-US.js +9 -1
  105. package/dist/localization/core-messages/es-ES.js +9 -1
  106. package/dist/localization/core-messages/fr-FR.js +9 -1
  107. package/dist/localization/core-messages/hi-IN.js +9 -1
  108. package/dist/localization/core-messages/ja-JP.js +9 -1
  109. package/dist/localization/core-messages/ko-KR.js +9 -1
  110. package/dist/localization/core-messages/pt-BR.js +9 -1
  111. package/dist/localization/core-messages/supplemental/ar-SA.js +1 -0
  112. package/dist/localization/core-messages/supplemental/de-DE.js +1 -0
  113. package/dist/localization/core-messages/supplemental/en-US.js +1 -0
  114. package/dist/localization/core-messages/supplemental/es-ES.js +1 -0
  115. package/dist/localization/core-messages/supplemental/fr-FR.js +1 -0
  116. package/dist/localization/core-messages/supplemental/hi-IN.js +1 -0
  117. package/dist/localization/core-messages/supplemental/ja-JP.js +1 -0
  118. package/dist/localization/core-messages/supplemental/ko-KR.js +1 -0
  119. package/dist/localization/core-messages/supplemental/pt-BR.js +1 -0
  120. package/dist/localization/core-messages/supplemental/zh-CN.js +1 -0
  121. package/dist/localization/core-messages/supplemental/zh-TW.js +1 -0
  122. package/dist/localization/core-messages/zh-CN.js +9 -1
  123. package/dist/localization/core-messages/zh-TW.js +9 -1
  124. package/dist/localization/core-messages.d.ts +9 -1
  125. package/dist/localization/mcp-settings-messages.d.ts +1 -0
  126. package/dist/mcp/connect-route.js +45 -8
  127. package/dist/mcp/connect-store.d.ts +7 -5
  128. package/dist/mcp/connect-store.js +30 -39
  129. package/dist/notifications/routes.d.ts +3 -3
  130. package/dist/observability/actions/get-observability-review-detail.d.ts +7 -0
  131. package/dist/observability/actions/get-observability-review-detail.js +25 -0
  132. package/dist/observability/reviews.d.ts +9 -1
  133. package/dist/observability/reviews.js +20 -0
  134. package/dist/observability/routes.d.ts +3 -3
  135. package/dist/observability/types.d.ts +8 -0
  136. package/dist/org/handlers.d.ts +1 -1
  137. package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
  138. package/dist/provider-api/actions/provider-api.d.ts +9 -9
  139. package/dist/scripts/db/query.js +6 -1
  140. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  141. package/dist/server/agent-engine-ollama-models-route.d.ts +2 -2
  142. package/dist/server/agent-teams.js +4 -1
  143. package/dist/server/auth.js +60 -20
  144. package/dist/server/better-auth-instance.js +7 -1
  145. package/dist/server/core-routes-plugin.d.ts +1 -1
  146. package/dist/server/core-routes-plugin.js +2 -0
  147. package/dist/server/embedded.js +6 -0
  148. package/dist/server/framework-request-handler.js +6 -0
  149. package/dist/server/realtime-token.d.ts +1 -1
  150. package/dist/usage/actions/get-usage-metrics.js +4 -1
  151. package/dist/usage/metrics-store.d.ts +19 -0
  152. package/dist/usage/metrics-store.js +106 -14
  153. package/dist/usage/store.d.ts +7 -2
  154. package/dist/usage/store.js +25 -4
  155. package/dist/vite/action-types-plugin.js +4 -0
  156. package/dist/vite/client.d.ts +6 -1
  157. package/dist/vite/client.js +40 -1
  158. package/package.json +4 -3
@@ -14,6 +14,7 @@ import {
14
14
  IconLayoutDistributeVertical,
15
15
  } from "@tabler/icons-react";
16
16
  import {
17
+ Fragment,
17
18
  type KeyboardEvent as ReactKeyboardEvent,
18
19
  type ReactNode,
19
20
  useState,
@@ -132,6 +133,14 @@ export interface AutoLayoutPadding {
132
133
  left: number;
133
134
  }
134
135
 
136
+ export type AutoLayoutMargin = AutoLayoutPadding;
137
+ export type AutoLayoutSidesMixed = Partial<
138
+ Record<keyof AutoLayoutPadding, boolean>
139
+ >;
140
+ export type AutoLayoutMarginTextValues = Partial<
141
+ Record<keyof AutoLayoutPadding, string>
142
+ >;
143
+
135
144
  const OPPOSITE_PADDING_SIDE: Record<
136
145
  keyof AutoLayoutPadding,
137
146
  keyof AutoLayoutPadding
@@ -154,10 +163,26 @@ export function mirrorPaddingChange(
154
163
  side: keyof AutoLayoutPadding,
155
164
  meta?: PaddingChangeMeta,
156
165
  ): AutoLayoutPadding {
157
- if (!meta?.altKey) return padding;
166
+ return mirrorSpacingChange(padding, side, meta);
167
+ }
168
+
169
+ export function mirrorMarginChange(
170
+ margin: AutoLayoutMargin,
171
+ side: keyof AutoLayoutPadding,
172
+ meta?: PaddingChangeMeta,
173
+ ): AutoLayoutMargin {
174
+ return mirrorSpacingChange(margin, side, meta);
175
+ }
176
+
177
+ function mirrorSpacingChange(
178
+ spacing: AutoLayoutPadding,
179
+ side: keyof AutoLayoutPadding,
180
+ meta?: PaddingChangeMeta,
181
+ ): AutoLayoutPadding {
182
+ if (!meta?.altKey) return spacing;
158
183
  return {
159
- ...padding,
160
- [OPPOSITE_PADDING_SIDE[side]]: padding[side],
184
+ ...spacing,
185
+ [OPPOSITE_PADDING_SIDE[side]]: spacing[side],
161
186
  };
162
187
  }
163
188
 
@@ -169,6 +194,9 @@ export interface AutoLayoutMatrixValue {
169
194
  gap: number;
170
195
  padding: AutoLayoutPadding;
171
196
  paddingLinked: boolean;
197
+ margin?: AutoLayoutMargin;
198
+ marginMixed?: AutoLayoutSidesMixed;
199
+ marginTextValues?: AutoLayoutMarginTextValues;
172
200
  clipContent?: boolean;
173
201
  clipContentMixed?: boolean;
174
202
  resolvedSize?: {
@@ -253,6 +281,13 @@ export interface AutoLayoutMatrixLabels {
253
281
  paddingRight: string;
254
282
  paddingBottom: string;
255
283
  paddingLeft: string;
284
+ margin: string;
285
+ linkMargin: string;
286
+ unlinkMargin: string;
287
+ marginTop: string;
288
+ marginRight: string;
289
+ marginBottom: string;
290
+ marginLeft: string;
256
291
  childSizing: string;
257
292
  hug: string;
258
293
  fill: string;
@@ -297,6 +332,11 @@ export interface AutoLayoutMatrixProps {
297
332
  meta?: ScrubInputChangeMeta,
298
333
  ) => void;
299
334
  onPaddingLinkedChange: (linked: boolean) => void;
335
+ onMarginChange?: (
336
+ margin: AutoLayoutMargin,
337
+ meta: ScrubInputChangeMeta | undefined,
338
+ changedSides: Array<keyof AutoLayoutMargin>,
339
+ ) => void;
300
340
  onClipContentChange?: (clipContent: boolean) => void;
301
341
  /** Clipping is a container's decision. A drawn rectangle or text node has
302
342
  * nothing to clip, so the control is meaningless on those. */
@@ -378,6 +418,13 @@ export const DEFAULT_AUTO_LAYOUT_LABELS: AutoLayoutMatrixLabels = {
378
418
  paddingRight: "Right", // i18n-ignore fallback component label
379
419
  paddingBottom: "Bottom", // i18n-ignore fallback component label
380
420
  paddingLeft: "Left", // i18n-ignore fallback component label
421
+ margin: "Margin", // i18n-ignore fallback component label
422
+ linkMargin: "Link margin sides", // i18n-ignore fallback component label
423
+ unlinkMargin: "Unlink margin sides", // i18n-ignore fallback component label
424
+ marginTop: "Top margin", // i18n-ignore fallback component label
425
+ marginRight: "Right margin", // i18n-ignore fallback component label
426
+ marginBottom: "Bottom margin", // i18n-ignore fallback component label
427
+ marginLeft: "Left margin", // i18n-ignore fallback component label
381
428
  childSizing: "Child sizing", // i18n-ignore fallback component label
382
429
  hug: "Hug", // i18n-ignore fallback component label
383
430
  fill: "Fill", // i18n-ignore fallback component label
@@ -419,6 +466,7 @@ export function AutoLayoutMatrix({
419
466
  onGridChange,
420
467
  onPaddingChange,
421
468
  onPaddingLinkedChange,
469
+ onMarginChange,
422
470
  onClipContentChange,
423
471
  clipContentSupported = true,
424
472
  onDistribute,
@@ -438,33 +486,6 @@ export function AutoLayoutMatrix({
438
486
  }: AutoLayoutMatrixProps) {
439
487
  const copy = { ...DEFAULT_AUTO_LAYOUT_LABELS, ...labels };
440
488
 
441
- // Show left/top as the representative value when padding is linked.
442
- // Averaging the two sides would silently destroy asymmetric padding on the
443
- // next edit — the onChange handler sets both sides to the same number, so
444
- // whatever is displayed becomes the new value for both sides. Using the
445
- // left/top value means scrubbing up/down from the current value preserves
446
- // the user's intent without a silent lossy round-trip through the average.
447
- const horizontalPaddingValue = value.padding.left;
448
- const verticalPaddingValue = value.padding.top;
449
- // The linked view shows one field per axis representing both sides, so it
450
- // must report "Mixed" whenever *either* side of that axis differs across
451
- // the selection — otherwise a selection with equal left/right but mixed
452
- // top/bottom (or vice versa) would silently show a real-looking number.
453
- const horizontalPaddingMixed = Boolean(
454
- value.paddingMixed?.left || value.paddingMixed?.right,
455
- );
456
- const verticalPaddingMixed = Boolean(
457
- value.paddingMixed?.top || value.paddingMixed?.bottom,
458
- );
459
-
460
- const updatePadding = (
461
- side: keyof AutoLayoutPadding,
462
- padding: AutoLayoutPadding,
463
- meta?: PaddingChangeMeta,
464
- ) => {
465
- onPaddingChange(mirrorPaddingChange(padding, side, meta), meta);
466
- };
467
-
468
489
  const activeFlow = getFlowOption(value);
469
490
  const isBlock = activeFlow === "normal";
470
491
  const canResizeToFit =
@@ -781,156 +802,37 @@ export function AutoLayoutMatrix({
781
802
 
782
803
  {/* ── Padding ── */}
783
804
  {showChildLayoutControls ? (
784
- <div className="design-sidebar-property-group">
785
- <ControlLabel>{copy.padding}</ControlLabel>
786
- {value.paddingLinked ? (
787
- /* Default linked state: 2 compact fields + link toggle */
788
- <InspectorGrid className="items-center" layout="action-pair">
789
- <InspectorGridCell span={11}>
790
- <PaddingField
791
- icon={IconPaddingHorizontal}
792
- ariaLabel={copy.paddingLeft + " / " + copy.paddingRight}
793
- value={horizontalPaddingValue}
794
- mixed={horizontalPaddingMixed}
795
- onChange={(next, meta) =>
796
- updatePadding(
797
- "left",
798
- {
799
- top: value.padding.top,
800
- bottom: value.padding.bottom,
801
- left: next,
802
- right: next,
803
- },
804
- meta,
805
- )
806
- }
807
- disabled={disabled}
808
- />
809
- </InspectorGridCell>
810
- <InspectorGridCell span={1} ariaHidden />
811
- <InspectorGridCell span={11}>
812
- <PaddingField
813
- icon={IconPaddingVertical}
814
- ariaLabel={copy.paddingTop + " / " + copy.paddingBottom}
815
- value={verticalPaddingValue}
816
- mixed={verticalPaddingMixed}
817
- onChange={(next, meta) =>
818
- updatePadding(
819
- "top",
820
- {
821
- top: next,
822
- bottom: next,
823
- left: value.padding.left,
824
- right: value.padding.right,
825
- },
826
- meta,
827
- )
828
- }
829
- disabled={disabled}
830
- />
831
- </InspectorGridCell>
832
- <InspectorGridCell span={1} ariaHidden />
833
- <InspectorGridCell span={4} className="flex justify-center">
834
- <PaddingLinkButton
835
- linked
836
- disabled={disabled}
837
- linkLabel={copy.linkPadding}
838
- unlinkLabel={copy.unlinkPadding}
839
- onToggle={() => onPaddingLinkedChange(false)}
840
- />
841
- </InspectorGridCell>
842
- </InspectorGrid>
843
- ) : (
844
- /* Unlinked state: expand to 4 separate T / R / B / L fields */
845
- <InspectorGrid className="items-center" layout="field-action">
846
- <InspectorGridCell span={24}>
847
- <InspectorGrid className="items-center" layout="pair">
848
- <InspectorGridCell span={INSPECTOR_GRID_ACTION_PAIR_SPAN}>
849
- <PaddingField
850
- icon={IconBorderTop}
851
- ariaLabel={copy.paddingTop}
852
- value={value.padding.top}
853
- mixed={value.paddingMixed?.top}
854
- onChange={(next, meta) =>
855
- updatePadding(
856
- "top",
857
- { ...value.padding, top: next },
858
- meta,
859
- )
860
- }
861
- disabled={disabled}
862
- />
863
- </InspectorGridCell>
864
- <InspectorGridCell
865
- span={INSPECTOR_GRID_PAIR_GUTTER_SPAN}
866
- ariaHidden
867
- />
868
- <InspectorGridCell span={INSPECTOR_GRID_ACTION_PAIR_SPAN}>
869
- <PaddingField
870
- icon={IconBorderRight}
871
- ariaLabel={copy.paddingRight}
872
- value={value.padding.right}
873
- mixed={value.paddingMixed?.right}
874
- onChange={(next, meta) =>
875
- updatePadding(
876
- "right",
877
- { ...value.padding, right: next },
878
- meta,
879
- )
880
- }
881
- disabled={disabled}
882
- />
883
- </InspectorGridCell>
884
- <InspectorGridCell span={INSPECTOR_GRID_ACTION_PAIR_SPAN}>
885
- <PaddingField
886
- icon={IconBorderBottom}
887
- ariaLabel={copy.paddingBottom}
888
- value={value.padding.bottom}
889
- mixed={value.paddingMixed?.bottom}
890
- onChange={(next, meta) =>
891
- updatePadding(
892
- "bottom",
893
- { ...value.padding, bottom: next },
894
- meta,
895
- )
896
- }
897
- disabled={disabled}
898
- />
899
- </InspectorGridCell>
900
- <InspectorGridCell
901
- span={INSPECTOR_GRID_PAIR_GUTTER_SPAN}
902
- ariaHidden
903
- />
904
- <InspectorGridCell span={INSPECTOR_GRID_ACTION_PAIR_SPAN}>
905
- <PaddingField
906
- icon={IconBorderLeft}
907
- ariaLabel={copy.paddingLeft}
908
- value={value.padding.left}
909
- mixed={value.paddingMixed?.left}
910
- onChange={(next, meta) =>
911
- updatePadding(
912
- "left",
913
- { ...value.padding, left: next },
914
- meta,
915
- )
916
- }
917
- disabled={disabled}
918
- />
919
- </InspectorGridCell>
920
- </InspectorGrid>
921
- </InspectorGridCell>
922
- <InspectorGridCell span={4} className="flex justify-center">
923
- <PaddingLinkButton
924
- linked={false}
925
- disabled={disabled}
926
- linkLabel={copy.linkPadding}
927
- unlinkLabel={copy.unlinkPadding}
928
- onToggle={() => onPaddingLinkedChange(true)}
929
- />
930
- </InspectorGridCell>
931
- </InspectorGrid>
932
- )}
933
- </div>
805
+ <FourSideSpacingProperties
806
+ label={copy.padding}
807
+ value={value.padding}
808
+ mixed={value.paddingMixed}
809
+ linked={value.paddingLinked}
810
+ linkLabel={copy.linkPadding}
811
+ unlinkLabel={copy.unlinkPadding}
812
+ sideLabels={{
813
+ top: copy.paddingTop,
814
+ right: copy.paddingRight,
815
+ bottom: copy.paddingBottom,
816
+ left: copy.paddingLeft,
817
+ }}
818
+ onLinkedChange={onPaddingLinkedChange}
819
+ onChange={onPaddingChange}
820
+ mirrorOppositeOnAlt
821
+ disabled={disabled}
822
+ />
823
+ ) : null}
824
+
825
+ {showChildLayoutControls && value.margin ? (
826
+ <MarginProperties
827
+ value={value.margin}
828
+ mixed={value.marginMixed}
829
+ textValues={value.marginTextValues}
830
+ labels={copy}
831
+ onChange={(margin, meta, changedSides) =>
832
+ onMarginChange?.(margin, meta, changedSides)
833
+ }
834
+ disabled={disabled}
835
+ />
934
836
  ) : null}
935
837
 
936
838
  {/* ── Clip content ── */}
@@ -1853,21 +1755,229 @@ function GapField({
1853
1755
  );
1854
1756
  }
1855
1757
 
1856
- /** A compact padding field: [icon] value. */
1857
- function PaddingField({
1758
+ interface FourSideSpacingPropertiesProps {
1759
+ label: string;
1760
+ value: AutoLayoutPadding;
1761
+ mixed?: AutoLayoutSidesMixed;
1762
+ textValues?: AutoLayoutMarginTextValues;
1763
+ linked: boolean;
1764
+ linkLabel: string;
1765
+ unlinkLabel: string;
1766
+ sideLabels: Record<keyof AutoLayoutPadding, string>;
1767
+ min?: number;
1768
+ disabled: boolean;
1769
+ onLinkedChange: (linked: boolean) => void;
1770
+ onChange: (
1771
+ value: AutoLayoutPadding,
1772
+ meta: ScrubInputChangeMeta | undefined,
1773
+ changedSides: Array<keyof AutoLayoutPadding>,
1774
+ ) => void;
1775
+ mirrorOppositeOnAlt?: boolean;
1776
+ }
1777
+
1778
+ function FourSideSpacingProperties({
1779
+ label,
1780
+ value,
1781
+ mixed,
1782
+ textValues,
1783
+ linked,
1784
+ linkLabel,
1785
+ unlinkLabel,
1786
+ sideLabels,
1787
+ min = 0,
1788
+ disabled,
1789
+ onLinkedChange,
1790
+ onChange,
1791
+ mirrorOppositeOnAlt = false,
1792
+ }: FourSideSpacingPropertiesProps) {
1793
+ const horizontalValue = value.left;
1794
+ const verticalValue = value.top;
1795
+ const horizontalMixed = Boolean(mixed?.left || mixed?.right);
1796
+ const verticalMixed = Boolean(mixed?.top || mixed?.bottom);
1797
+
1798
+ const update = (
1799
+ side: keyof AutoLayoutPadding,
1800
+ next: AutoLayoutPadding,
1801
+ meta?: PaddingChangeMeta,
1802
+ ) => {
1803
+ const changedSides = new Set<keyof AutoLayoutPadding>([side]);
1804
+ if (linked || (mirrorOppositeOnAlt && meta?.altKey)) {
1805
+ changedSides.add(OPPOSITE_PADDING_SIDE[side]);
1806
+ }
1807
+ onChange(
1808
+ mirrorOppositeOnAlt ? mirrorSpacingChange(next, side, meta) : next,
1809
+ meta,
1810
+ [...changedSides],
1811
+ );
1812
+ };
1813
+
1814
+ return (
1815
+ <div className="design-sidebar-property-group">
1816
+ <ControlLabel>{label}</ControlLabel>
1817
+ {linked ? (
1818
+ <InspectorGrid className="items-center" layout="action-pair">
1819
+ <InspectorGridCell span={11}>
1820
+ <SpacingField
1821
+ icon={IconPaddingHorizontal}
1822
+ ariaLabel={sideLabels.left + " / " + sideLabels.right}
1823
+ value={horizontalValue}
1824
+ textValue={textValues?.left}
1825
+ mixed={horizontalMixed}
1826
+ min={min}
1827
+ onChange={(next, meta) =>
1828
+ update("left", { ...value, left: next, right: next }, meta)
1829
+ }
1830
+ disabled={disabled}
1831
+ />
1832
+ </InspectorGridCell>
1833
+ <InspectorGridCell span={1} ariaHidden />
1834
+ <InspectorGridCell span={11}>
1835
+ <SpacingField
1836
+ icon={IconPaddingVertical}
1837
+ ariaLabel={sideLabels.top + " / " + sideLabels.bottom}
1838
+ value={verticalValue}
1839
+ textValue={textValues?.top}
1840
+ mixed={verticalMixed}
1841
+ min={min}
1842
+ onChange={(next, meta) =>
1843
+ update("top", { ...value, top: next, bottom: next }, meta)
1844
+ }
1845
+ disabled={disabled}
1846
+ />
1847
+ </InspectorGridCell>
1848
+ <InspectorGridCell span={1} ariaHidden />
1849
+ <InspectorGridCell span={4} className="flex justify-center">
1850
+ <SpacingLinkButton
1851
+ linked
1852
+ disabled={disabled}
1853
+ linkLabel={linkLabel}
1854
+ unlinkLabel={unlinkLabel}
1855
+ onToggle={() => onLinkedChange(false)}
1856
+ />
1857
+ </InspectorGridCell>
1858
+ </InspectorGrid>
1859
+ ) : (
1860
+ <InspectorGrid className="items-center" layout="field-action">
1861
+ <InspectorGridCell span={24}>
1862
+ <InspectorGrid className="items-center" layout="pair">
1863
+ {(
1864
+ [
1865
+ ["top", IconBorderTop],
1866
+ ["right", IconBorderRight],
1867
+ ["bottom", IconBorderBottom],
1868
+ ["left", IconBorderLeft],
1869
+ ] as const
1870
+ ).map(([side, icon], index) => (
1871
+ <Fragment key={side}>
1872
+ {index % 2 === 1 ? (
1873
+ <InspectorGridCell
1874
+ span={INSPECTOR_GRID_PAIR_GUTTER_SPAN}
1875
+ ariaHidden
1876
+ />
1877
+ ) : null}
1878
+ <InspectorGridCell span={INSPECTOR_GRID_ACTION_PAIR_SPAN}>
1879
+ <SpacingField
1880
+ icon={icon}
1881
+ ariaLabel={sideLabels[side]}
1882
+ value={value[side]}
1883
+ textValue={textValues?.[side]}
1884
+ mixed={mixed?.[side]}
1885
+ min={min}
1886
+ onChange={(next, meta) =>
1887
+ update(side, { ...value, [side]: next }, meta)
1888
+ }
1889
+ disabled={disabled}
1890
+ />
1891
+ </InspectorGridCell>
1892
+ </Fragment>
1893
+ ))}
1894
+ </InspectorGrid>
1895
+ </InspectorGridCell>
1896
+ <InspectorGridCell span={4} className="flex justify-center">
1897
+ <SpacingLinkButton
1898
+ linked={false}
1899
+ disabled={disabled}
1900
+ linkLabel={linkLabel}
1901
+ unlinkLabel={unlinkLabel}
1902
+ onToggle={() => onLinkedChange(true)}
1903
+ />
1904
+ </InspectorGridCell>
1905
+ </InspectorGrid>
1906
+ )}
1907
+ </div>
1908
+ );
1909
+ }
1910
+
1911
+ export function MarginProperties({
1912
+ value,
1913
+ mixed,
1914
+ textValues,
1915
+ labels,
1916
+ onChange,
1917
+ disabled = false,
1918
+ }: {
1919
+ value: AutoLayoutMargin;
1920
+ mixed?: AutoLayoutSidesMixed;
1921
+ textValues?: AutoLayoutMarginTextValues;
1922
+ labels?: Partial<AutoLayoutMatrixLabels>;
1923
+ onChange: (
1924
+ margin: AutoLayoutMargin,
1925
+ meta: ScrubInputChangeMeta | undefined,
1926
+ changedSides: Array<keyof AutoLayoutMargin>,
1927
+ ) => void;
1928
+ disabled?: boolean;
1929
+ }) {
1930
+ const copy = { ...DEFAULT_AUTO_LAYOUT_LABELS, ...labels };
1931
+ const [linked, setLinked] = useState(() => {
1932
+ const sides = ["top", "right", "bottom", "left"] as const;
1933
+ if (sides.some((side) => mixed?.[side])) return false;
1934
+ const first = textValues?.top ?? String(value.top);
1935
+ return sides.every(
1936
+ (side) => (textValues?.[side] ?? String(value[side])) === first,
1937
+ );
1938
+ });
1939
+
1940
+ return (
1941
+ <FourSideSpacingProperties
1942
+ label={copy.margin}
1943
+ value={value}
1944
+ mixed={mixed}
1945
+ textValues={textValues}
1946
+ linked={linked}
1947
+ linkLabel={copy.linkMargin}
1948
+ unlinkLabel={copy.unlinkMargin}
1949
+ sideLabels={{
1950
+ top: copy.marginTop,
1951
+ right: copy.marginRight,
1952
+ bottom: copy.marginBottom,
1953
+ left: copy.marginLeft,
1954
+ }}
1955
+ min={-999}
1956
+ disabled={disabled}
1957
+ onLinkedChange={setLinked}
1958
+ onChange={onChange}
1959
+ mirrorOppositeOnAlt
1960
+ />
1961
+ );
1962
+ }
1963
+
1964
+ /** A compact spacing field: [icon] value. */
1965
+ function SpacingField({
1858
1966
  icon: Icon,
1859
1967
  ariaLabel,
1860
1968
  value,
1969
+ textValue,
1861
1970
  mixed = false,
1971
+ min = 0,
1862
1972
  onChange,
1863
1973
  disabled,
1864
1974
  }: {
1865
1975
  icon: (props: { className?: string }) => ReactNode;
1866
1976
  ariaLabel: string;
1867
1977
  value: number;
1868
- /** Set for a multi-selection with differing padding on this side — see AutoLayoutMatrixValue.paddingMixed. */
1978
+ textValue?: string;
1869
1979
  mixed?: boolean;
1870
- /** Forwards ScrubInput's gesture meta — see AutoLayoutMatrixProps.onPaddingChange. */
1980
+ min?: number;
1871
1981
  onChange: (value: number, meta?: ScrubInputChangeMeta) => void;
1872
1982
  disabled: boolean;
1873
1983
  }) {
@@ -1884,10 +1994,11 @@ function PaddingField({
1884
1994
  tooltipLabel={ariaLabel}
1885
1995
  icon={Icon}
1886
1996
  value={value}
1997
+ textValue={textValue}
1887
1998
  mixed={mixed}
1888
1999
  onChange={(next, meta) => onChange(next, meta)}
1889
2000
  unit="px"
1890
- min={0}
2001
+ min={min}
1891
2002
  step={1}
1892
2003
  precision={1}
1893
2004
  disabled={disabled}
@@ -1899,8 +2010,8 @@ function PaddingField({
1899
2010
  );
1900
2011
  }
1901
2012
 
1902
- /** Link / unlink padding toggle button. */
1903
- function PaddingLinkButton({
2013
+ /** Link / unlink four-side spacing toggle button. */
2014
+ function SpacingLinkButton({
1904
2015
  linked,
1905
2016
  disabled,
1906
2017
  linkLabel,
@@ -816,7 +816,7 @@ export function GradientEditor({
816
816
  }
817
817
  commitPositionDraft();
818
818
  }}
819
- className="h-full min-w-0 flex-1 bg-transparent px-1.5 !text-[11px] tabular-nums focus-visible:outline-none"
819
+ className="h-full min-w-0 flex-1 bg-transparent px-1.5 !text-[11px] tabular-nums [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none focus-visible:outline-none"
820
820
  />
821
821
  </div>
822
822
 
@@ -858,7 +858,7 @@ export function GradientEditor({
858
858
  setAngleInput(null);
859
859
  if (changed) onCommit?.();
860
860
  }}
861
- className="h-full min-w-0 flex-1 bg-transparent px-1.5 !text-[11px] tabular-nums focus-visible:outline-none"
861
+ className="h-full min-w-0 flex-1 bg-transparent px-1.5 !text-[11px] tabular-nums [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none focus-visible:outline-none"
862
862
  />
863
863
  <span className="flex w-4 shrink-0 items-center justify-center text-[10px] text-muted-foreground">
864
864
  °
@@ -13,14 +13,18 @@ export {
13
13
  } from "./BreakpointOverrideIndicator";
14
14
  export {
15
15
  AutoLayoutMatrix,
16
+ MarginProperties,
16
17
  type AutoLayoutDirection,
17
18
  type AutoLayoutFlow,
18
19
  type AutoLayoutGridTrackSizing,
19
20
  type AutoLayoutGridValue,
21
+ type AutoLayoutMargin,
22
+ type AutoLayoutMarginTextValues,
20
23
  type AutoLayoutMatrixLabels,
21
24
  type AutoLayoutMatrixProps,
22
25
  type AutoLayoutMatrixValue,
23
26
  type AutoLayoutPadding,
27
+ type AutoLayoutSidesMixed,
24
28
  type AutoLayoutSizing,
25
29
  type AutoLayoutSizingAxis,
26
30
  type AutoLayoutWrap,
@@ -214,9 +214,11 @@ export interface MultiScreenCanvasProps {
214
214
  /** Lets every live frame receive native pointer interaction while the
215
215
  * overview camera and frame chrome remain available. */
216
216
  interactMode?: boolean;
217
- /** One overview screen may temporarily pass pointer input through its
218
- * mounted live editor without remounting or changing the global view. */
217
+ /** Screen whose mounted editor receives input in focused Interact view. */
219
218
  interactScreenId?: string | null;
219
+ /** Responsive viewport for a focused Interact screen that stays in this
220
+ * mounted canvas. */
221
+ focusedInteractViewport?: { width: number; height: number } | null;
220
222
  /** Viewer mode keeps selection/inspection available without edit chrome. */
221
223
  readOnly?: boolean;
222
224
  /** Live localhost screens whose DOM editor may receive pointer input. */
@@ -262,6 +262,12 @@ export default {
262
262
  opacity: "Opacity",
263
263
  padding: "Padding",
264
264
  margin: "Margin",
265
+ marginTop: "Top margin",
266
+ marginRight: "Right margin",
267
+ marginBottom: "Bottom margin",
268
+ marginLeft: "Left margin",
269
+ linkMarginSides: "Link margin sides",
270
+ unlinkMarginSides: "Unlink margin sides",
265
271
  radius: "Radius",
266
272
  flexGrow: "Grow",
267
273
  flexShrink: "Shrink",