@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
|
@@ -16,7 +16,7 @@ export interface StylesChangeArgs {
|
|
|
16
16
|
styles: Record<string, string>,
|
|
17
17
|
) => boolean;
|
|
18
18
|
commitRelativeStyleDeltaToSelectedLayers: (
|
|
19
|
-
property: string,
|
|
19
|
+
property: string | string[],
|
|
20
20
|
operation: number | ScrubRelativeExpression,
|
|
21
21
|
phase?: StyleChangeMeta["phase"],
|
|
22
22
|
) => boolean;
|
|
@@ -78,7 +78,7 @@ export function runStylesChange(
|
|
|
78
78
|
if (meta?.capturedStyleTargets && meta.phase !== "preview") {
|
|
79
79
|
commitCapturedStyleTargets(
|
|
80
80
|
Object.fromEntries(
|
|
81
|
-
Object.entries(styles).filter(([, value]) =>
|
|
81
|
+
Object.entries(styles).filter(([, value]) => value !== undefined),
|
|
82
82
|
),
|
|
83
83
|
meta.capturedStyleTargets,
|
|
84
84
|
meta.interactionState,
|
|
@@ -115,16 +115,29 @@ export function runStylesChange(
|
|
|
115
115
|
return;
|
|
116
116
|
}
|
|
117
117
|
const selector = selectedElement?.selector ?? "body";
|
|
118
|
-
const entries = Object.entries(styles).filter(
|
|
118
|
+
const entries = Object.entries(styles).filter(
|
|
119
|
+
([, value]) => value !== undefined,
|
|
120
|
+
);
|
|
119
121
|
if (entries.length === 0) return;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
const relativeProperties =
|
|
123
|
+
meta?.relativeDeltaProperties ??
|
|
124
|
+
(entries.length === 1 ? [entries[0]![0]] : []);
|
|
125
|
+
const relativeOperation = meta?.relativeExpression ?? meta?.relativeDelta;
|
|
126
|
+
if (
|
|
127
|
+
relativeOperation !== undefined &&
|
|
128
|
+
relativeProperties.length > 0 &&
|
|
129
|
+
relativeProperties.every((property) =>
|
|
130
|
+
entries.some(([entryProperty]) => entryProperty === property),
|
|
131
|
+
)
|
|
132
|
+
) {
|
|
133
|
+
const applied = commitRelativeStyleDeltaToSelectedLayers(
|
|
134
|
+
relativeProperties.length === 1
|
|
135
|
+
? relativeProperties[0]!
|
|
136
|
+
: relativeProperties,
|
|
137
|
+
relativeOperation,
|
|
138
|
+
meta?.phase,
|
|
126
139
|
);
|
|
127
|
-
return;
|
|
140
|
+
if (meta?.relativeExpression || applied) return;
|
|
128
141
|
}
|
|
129
142
|
const target = styleWriteTarget({ selector, selectedElement });
|
|
130
143
|
// T10: mirror handleStyleChange's text-range routing here. Without
|
|
@@ -169,26 +182,6 @@ export function runStylesChange(
|
|
|
169
182
|
}
|
|
170
183
|
return;
|
|
171
184
|
}
|
|
172
|
-
// Mixed-value arrow-step parity (item 7): see handleStyleChange's
|
|
173
|
-
// matching comment for the full defensive-read rationale. A relative
|
|
174
|
-
// delta is inherently single-valued (one scrub gesture on one field),
|
|
175
|
-
// so this only applies when the batched patch has exactly one entry —
|
|
176
|
-
// a multi-property patch (e.g. a shadow popover's X+Y+blur+spread all
|
|
177
|
-
// at once) has no single delta to apply per-node and falls through to
|
|
178
|
-
// the existing absolute-value paths unchanged.
|
|
179
|
-
const relativeDelta = (meta as { relativeDelta?: number } | undefined)
|
|
180
|
-
?.relativeDelta;
|
|
181
|
-
if (typeof relativeDelta === "number" && entries.length === 1) {
|
|
182
|
-
const [singleProperty] = entries[0]!;
|
|
183
|
-
if (
|
|
184
|
-
commitRelativeStyleDeltaToSelectedLayers(
|
|
185
|
-
singleProperty,
|
|
186
|
-
relativeDelta,
|
|
187
|
-
meta?.phase,
|
|
188
|
-
)
|
|
189
|
-
)
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
185
|
if (
|
|
193
186
|
selectedElement &&
|
|
194
187
|
commitStylesToSelectedLayers(Object.fromEntries(entries), meta?.phase)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { parseCssColor, rgbaToCss } from "@shared/color-utils";
|
|
2
|
+
|
|
3
|
+
import { isVectorShapeElement } from "@/components/design/edit-panel/element-classification";
|
|
4
|
+
import type { ElementInfo } from "@/components/design/types";
|
|
5
|
+
|
|
6
|
+
export interface SwapFillStrokeArgs {
|
|
7
|
+
canEditDesign: boolean;
|
|
8
|
+
selectedElement: ElementInfo | null;
|
|
9
|
+
handleStylesChange: (styles: Record<string, string>) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function hasPaint(value: string): boolean {
|
|
13
|
+
const normalized = value.trim().toLowerCase();
|
|
14
|
+
if (!normalized || normalized === "none" || normalized === "transparent") {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
const color = parseCssColor(value);
|
|
18
|
+
return color ? color.a > 0 : true;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function swappedPaint(
|
|
22
|
+
paint: string,
|
|
23
|
+
opacity: string | undefined,
|
|
24
|
+
): { paint: string; opacity: string } {
|
|
25
|
+
const alpha = Number.parseFloat(opacity ?? "1");
|
|
26
|
+
if (!Number.isFinite(alpha) || alpha >= 1) return { paint, opacity: "" };
|
|
27
|
+
const color = parseCssColor(paint);
|
|
28
|
+
if (color) {
|
|
29
|
+
return { paint: rgbaToCss({ ...color, a: color.a * alpha }), opacity: "" };
|
|
30
|
+
}
|
|
31
|
+
// A gradient value has nowhere to carry alpha, so it keeps its opacity.
|
|
32
|
+
return { paint, opacity: String(alpha) };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Figma's Shift+X. Matches Figma even when one side is empty: an element with
|
|
37
|
+
* a fill and no stroke ends up with a stroke and no fill (not a no-op). Both
|
|
38
|
+
* sides commit together so the swap is a single undo step.
|
|
39
|
+
*/
|
|
40
|
+
export function runSwapFillStroke({
|
|
41
|
+
canEditDesign,
|
|
42
|
+
selectedElement,
|
|
43
|
+
handleStylesChange,
|
|
44
|
+
}: SwapFillStrokeArgs): void {
|
|
45
|
+
if (!canEditDesign || !selectedElement) return;
|
|
46
|
+
if (isVectorShapeElement(selectedElement)) {
|
|
47
|
+
const styles = selectedElement.computedStyles;
|
|
48
|
+
// SVG gradient styles render as url(#id), but the editor's authored
|
|
49
|
+
// custom properties hold the portable gradient values. Capture both
|
|
50
|
+
// before committing either side so Shift+X can rebuild each definition
|
|
51
|
+
// without leaving the other paint pointing at a removed id.
|
|
52
|
+
const fill =
|
|
53
|
+
selectedElement.inlineStyles?.["--an-vector-fill-gradient"] ||
|
|
54
|
+
styles["--an-vector-fill-gradient"] ||
|
|
55
|
+
styles.fill ||
|
|
56
|
+
"";
|
|
57
|
+
const stroke =
|
|
58
|
+
selectedElement.inlineStyles?.["--an-vector-stroke-gradient"] ||
|
|
59
|
+
styles["--an-vector-stroke-gradient"] ||
|
|
60
|
+
styles.stroke ||
|
|
61
|
+
"";
|
|
62
|
+
const fillHasPaint = hasPaint(fill);
|
|
63
|
+
const strokeWidth = Number.parseFloat(styles.strokeWidth ?? "0");
|
|
64
|
+
const strokeOpacity = Number.parseFloat(styles.strokeOpacity ?? "1");
|
|
65
|
+
const strokeHasPaint =
|
|
66
|
+
hasPaint(stroke) && strokeWidth > 0 && strokeOpacity > 0;
|
|
67
|
+
if (!fillHasPaint && !strokeHasPaint) return;
|
|
68
|
+
// Opacity rides in the colours where it can: an inline fill-opacity
|
|
69
|
+
// would override the fill-opacity="0" that keeps an open path's chord
|
|
70
|
+
// unpainted. An empty value removes the old declaration.
|
|
71
|
+
const nextFill = strokeHasPaint
|
|
72
|
+
? swappedPaint(stroke, styles.strokeOpacity)
|
|
73
|
+
: { paint: "none", opacity: "" };
|
|
74
|
+
const nextStroke = fillHasPaint
|
|
75
|
+
? swappedPaint(fill, styles.fillOpacity)
|
|
76
|
+
: { paint: "none", opacity: "" };
|
|
77
|
+
// Removing only the inline value would leave an imported SVG's
|
|
78
|
+
// presentation opacity multiplying the swapped colour.
|
|
79
|
+
const opacityFor = (
|
|
80
|
+
next: { paint: string; opacity: string },
|
|
81
|
+
current: string | undefined,
|
|
82
|
+
) =>
|
|
83
|
+
next.opacity === "" &&
|
|
84
|
+
next.paint !== "none" &&
|
|
85
|
+
Number.parseFloat(current ?? "1") < 1
|
|
86
|
+
? "1"
|
|
87
|
+
: next.opacity;
|
|
88
|
+
const fillOpacity = opacityFor(nextFill, styles.fillOpacity);
|
|
89
|
+
const strokeOpacityValue = opacityFor(nextStroke, styles.strokeOpacity);
|
|
90
|
+
handleStylesChange({
|
|
91
|
+
fill: nextFill.paint,
|
|
92
|
+
fillOpacity,
|
|
93
|
+
stroke: nextStroke.paint,
|
|
94
|
+
strokeOpacity: strokeOpacityValue,
|
|
95
|
+
...(fillHasPaint && !strokeHasPaint ? { strokeWidth: "1px" } : {}),
|
|
96
|
+
});
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const currentFill = selectedElement.computedStyles.backgroundColor ?? "";
|
|
100
|
+
const currentStroke = selectedElement.computedStyles.borderColor ?? "";
|
|
101
|
+
handleStylesChange({
|
|
102
|
+
backgroundColor: currentStroke || "transparent",
|
|
103
|
+
borderColor: currentFill || "transparent",
|
|
104
|
+
});
|
|
105
|
+
}
|
|
@@ -1764,6 +1764,27 @@ export function applyScopedVisualStyleEdit(args: {
|
|
|
1764
1764
|
source,
|
|
1765
1765
|
} = args;
|
|
1766
1766
|
const normalizedProperty = normalizeCssPropertyName(property);
|
|
1767
|
+
// An empty value clears the declaration at the edit's own scope.
|
|
1768
|
+
if (value === "") {
|
|
1769
|
+
return applyVisualEdit(
|
|
1770
|
+
content,
|
|
1771
|
+
upperBoundPx == null
|
|
1772
|
+
? {
|
|
1773
|
+
kind: "style",
|
|
1774
|
+
operation: "remove",
|
|
1775
|
+
target,
|
|
1776
|
+
property: normalizedProperty,
|
|
1777
|
+
}
|
|
1778
|
+
: {
|
|
1779
|
+
kind: "breakpoint-style",
|
|
1780
|
+
operation: "remove",
|
|
1781
|
+
target,
|
|
1782
|
+
maxWidthPx: upperBoundPx,
|
|
1783
|
+
property: normalizedProperty,
|
|
1784
|
+
},
|
|
1785
|
+
{ source },
|
|
1786
|
+
);
|
|
1787
|
+
}
|
|
1767
1788
|
if (upperBoundPx != null && isVectorEndpointProperty(normalizedProperty)) {
|
|
1768
1789
|
// Endpoint values require marker definitions and shape attributes, which
|
|
1769
1790
|
// cannot be represented by a media-scoped custom property. Let the shared
|
|
@@ -5,9 +5,15 @@ export interface PresentReviewState {
|
|
|
5
5
|
|
|
6
6
|
export function resolvePresentEscapeAction(
|
|
7
7
|
state: PresentReviewState,
|
|
8
|
-
|
|
8
|
+
reviewEmbed = false,
|
|
9
|
+
):
|
|
10
|
+
| "close-comments"
|
|
11
|
+
| "defer-to-comment-mode"
|
|
12
|
+
| "stay-in-review-embed"
|
|
13
|
+
| "exit-presentation" {
|
|
9
14
|
if (state.commentsOpen) return "close-comments";
|
|
10
15
|
if (state.commentMode) return "defer-to-comment-mode";
|
|
16
|
+
if (reviewEmbed) return "stay-in-review-embed";
|
|
11
17
|
return "exit-presentation";
|
|
12
18
|
}
|
|
13
19
|
|
|
@@ -457,6 +457,12 @@ export async function enterInteractView(
|
|
|
457
457
|
"data-screen-interact-mode",
|
|
458
458
|
"false",
|
|
459
459
|
);
|
|
460
|
+
const previewIframe = screenShell
|
|
461
|
+
.locator(DESIGN_PREVIEW_IFRAME_SELECTOR)
|
|
462
|
+
.first();
|
|
463
|
+
await expect(previewIframe).toBeVisible();
|
|
464
|
+
const previewIframeHandle = await previewIframe.elementHandle();
|
|
465
|
+
if (!previewIframeHandle) throw new Error("screen preview iframe is missing");
|
|
460
466
|
// The screen card can extend beneath the fixed inspector at narrow canvas
|
|
461
467
|
// widths; invoke the button without relying on the panel's overlapping
|
|
462
468
|
// physical hit area.
|
|
@@ -467,8 +473,19 @@ export async function enterInteractView(
|
|
|
467
473
|
"data-screen-interact-mode",
|
|
468
474
|
"true",
|
|
469
475
|
);
|
|
470
|
-
|
|
471
|
-
|
|
476
|
+
await expect(
|
|
477
|
+
page.getByRole("button", { name: "Exit responsive preview" }),
|
|
478
|
+
).toBeVisible();
|
|
479
|
+
expect(
|
|
480
|
+
await previewIframeHandle.evaluate((before) =>
|
|
481
|
+
Boolean(
|
|
482
|
+
before
|
|
483
|
+
.closest("[data-screen-shell]")
|
|
484
|
+
?.querySelector("iframe[data-design-preview-iframe]") === before,
|
|
485
|
+
),
|
|
486
|
+
),
|
|
487
|
+
).toBe(true);
|
|
488
|
+
// The focused responsive preview keeps its original iframe in the DOM.
|
|
472
489
|
await expect
|
|
473
490
|
.poll(
|
|
474
491
|
async () =>
|
|
@@ -843,7 +843,7 @@ export function ComposeModal({
|
|
|
843
843
|
? "bottom-0 h-11 rounded-t-xl sm:w-[540px]"
|
|
844
844
|
: isExpanded
|
|
845
845
|
? "top-0 bottom-0 h-auto rounded-none sm:top-4 sm:bottom-4 sm:w-[min(960px,calc(100vw-var(--compose-right)-1rem))] sm:rounded-xl"
|
|
846
|
-
: "bottom-0 h-[100dvh] sm:h-[min(540px,_calc(100dvh_-_2rem))]
|
|
846
|
+
: "bottom-0 h-[100dvh] sm:h-[min(540px,_calc(100dvh_-_2rem))] md:w-[min(490px,_calc(100vw_-_var(--compose-right)_-_1rem))] sm:rounded-xl",
|
|
847
847
|
)}
|
|
848
848
|
data-mail-compose
|
|
849
849
|
style={composeStyle}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
bigint,
|
|
3
|
+
index,
|
|
4
|
+
integer,
|
|
5
|
+
table,
|
|
6
|
+
text,
|
|
7
|
+
} from "@agent-native/core/db/schema";
|
|
2
8
|
|
|
3
9
|
/**
|
|
4
10
|
* Short-lived, owner-scoped continuation state for the external Mail
|
|
@@ -111,6 +117,9 @@ export const mailSyncAccounts = table(
|
|
|
111
117
|
.default("idle"),
|
|
112
118
|
lastError: text("last_error"),
|
|
113
119
|
lastSyncedAt: integer("last_synced_at"),
|
|
120
|
+
lastPushGeneration: bigint("last_push_generation", { mode: "number" })
|
|
121
|
+
.notNull()
|
|
122
|
+
.default(0),
|
|
114
123
|
syncClaimId: text("sync_claim_id"),
|
|
115
124
|
syncClaimedAt: integer("sync_claimed_at"),
|
|
116
125
|
// Compact cached labels.list result: [{id,name,type,color?,messagesTotal?,
|
|
@@ -123,6 +132,22 @@ export const mailSyncAccounts = table(
|
|
|
123
132
|
(t) => [index("mail_sync_accounts_owner_idx").on(t.ownerEmail)],
|
|
124
133
|
);
|
|
125
134
|
|
|
135
|
+
export const mailInboxPushInvalidations = table(
|
|
136
|
+
"mail_inbox_push_invalidations",
|
|
137
|
+
{
|
|
138
|
+
id: text("id").primaryKey(),
|
|
139
|
+
ownerEmail: text("owner_email").notNull(),
|
|
140
|
+
accountEmail: text("account_email").notNull(),
|
|
141
|
+
generation: bigint("generation", { mode: "number" }).notNull().default(1),
|
|
142
|
+
},
|
|
143
|
+
(t) => [
|
|
144
|
+
index("mail_inbox_push_invalidations_owner_account_idx").on(
|
|
145
|
+
t.ownerEmail,
|
|
146
|
+
t.accountEmail,
|
|
147
|
+
),
|
|
148
|
+
],
|
|
149
|
+
);
|
|
150
|
+
|
|
126
151
|
/**
|
|
127
152
|
* SQL mirror of each connected account's INBOX threads, kept fresh by
|
|
128
153
|
* `server/lib/inbox-sync.ts`. Metadata only — no bodies, no HTML.
|
|
@@ -69,6 +69,7 @@ export type SyncAccountRow = {
|
|
|
69
69
|
status: "idle" | "syncing" | "error" | "needs_reauth";
|
|
70
70
|
lastError: string | null;
|
|
71
71
|
lastSyncedAt: number | null;
|
|
72
|
+
lastPushGeneration: number;
|
|
72
73
|
syncClaimId: string | null;
|
|
73
74
|
syncClaimedAt: number | null;
|
|
74
75
|
labels: CachedGmailLabel[] | null;
|
|
@@ -144,6 +145,7 @@ function toSyncAccountRow(
|
|
|
144
145
|
status: row.status as SyncAccountRow["status"],
|
|
145
146
|
lastError: row.lastError,
|
|
146
147
|
lastSyncedAt: row.lastSyncedAt,
|
|
148
|
+
lastPushGeneration: row.lastPushGeneration,
|
|
147
149
|
syncClaimId: row.syncClaimId,
|
|
148
150
|
syncClaimedAt: row.syncClaimedAt,
|
|
149
151
|
labels: row.labelsJson
|
|
@@ -631,6 +633,7 @@ export type ThreadUpsertInput = {
|
|
|
631
633
|
|
|
632
634
|
export async function upsertInboxThreadRows(
|
|
633
635
|
rows: ThreadUpsertInput[],
|
|
636
|
+
db: any = getDb(),
|
|
634
637
|
): Promise<void> {
|
|
635
638
|
if (rows.length === 0) return;
|
|
636
639
|
const now = Date.now();
|
|
@@ -670,7 +673,7 @@ export async function upsertInboxThreadRows(
|
|
|
670
673
|
localMutationFields: null,
|
|
671
674
|
}));
|
|
672
675
|
|
|
673
|
-
await
|
|
676
|
+
await db
|
|
674
677
|
.insert(schema.mailInboxThreads)
|
|
675
678
|
.values(values)
|
|
676
679
|
.onConflictDoUpdate({
|
|
@@ -747,6 +750,7 @@ export async function deleteInboxThreadRow(
|
|
|
747
750
|
accountEmail: string,
|
|
748
751
|
threadId: string,
|
|
749
752
|
readStartedAt?: number,
|
|
753
|
+
db: any = getDb(),
|
|
750
754
|
): Promise<void> {
|
|
751
755
|
const conditions = [
|
|
752
756
|
eq(
|
|
@@ -757,9 +761,7 @@ export async function deleteInboxThreadRow(
|
|
|
757
761
|
if (readStartedAt !== undefined) {
|
|
758
762
|
conditions.push(lt(schema.mailInboxThreads.updatedAt, readStartedAt));
|
|
759
763
|
}
|
|
760
|
-
await
|
|
761
|
-
.delete(schema.mailInboxThreads)
|
|
762
|
-
.where(and(...conditions));
|
|
764
|
+
await db.delete(schema.mailInboxThreads).where(and(...conditions));
|
|
763
765
|
}
|
|
764
766
|
|
|
765
767
|
/**
|
|
@@ -770,8 +772,9 @@ export async function markThreadsOutOfInboxBeforeSync(
|
|
|
770
772
|
ownerEmail: string,
|
|
771
773
|
accountEmail: string,
|
|
772
774
|
cutoffSyncedAt: number,
|
|
775
|
+
db: any = getDb(),
|
|
773
776
|
): Promise<void> {
|
|
774
|
-
await
|
|
777
|
+
await db
|
|
775
778
|
.update(schema.mailInboxThreads)
|
|
776
779
|
.set({ inInbox: 0, updatedAt: Date.now() })
|
|
777
780
|
.where(
|
|
@@ -827,26 +830,25 @@ export class SyncClaimLostError extends Error {
|
|
|
827
830
|
}
|
|
828
831
|
}
|
|
829
832
|
|
|
830
|
-
|
|
831
|
-
* Guards a batch of row writes (`upsertInboxThreadRows`,
|
|
832
|
-
* `markThreadsOutOfInboxBeforeSync`, `deleteInboxThreadRow`) that have no
|
|
833
|
-
* `sync_claim_id` column of their own to fence against, unlike
|
|
834
|
-
* {@link patchSyncAccount}'s `opts.claimId`. One SELECT immediately before
|
|
835
|
-
* the write; throws {@link SyncClaimLostError} when the claim no longer
|
|
836
|
-
* matches instead of letting a lapsed worker overwrite a newer worker's rows.
|
|
837
|
-
*/
|
|
838
|
-
export async function assertSyncClaimHeld(
|
|
833
|
+
export async function withSyncClaim<T>(
|
|
839
834
|
ownerEmail: string,
|
|
840
835
|
accountEmail: string,
|
|
841
836
|
claimId: string,
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
837
|
+
write: (tx: any) => Promise<T>,
|
|
838
|
+
): Promise<T> {
|
|
839
|
+
return getDb().transaction(async (tx: any) => {
|
|
840
|
+
const rows = await tx
|
|
841
|
+
.select({ syncClaimId: schema.mailSyncAccounts.syncClaimId })
|
|
842
|
+
.from(schema.mailSyncAccounts)
|
|
843
|
+
.where(eq(schema.mailSyncAccounts.id, rowId(ownerEmail, accountEmail)))
|
|
844
|
+
.for("update")
|
|
845
|
+
.limit(1);
|
|
846
|
+
if (rows[0]?.syncClaimId !== claimId)
|
|
847
|
+
throw new SyncClaimLostError(accountEmail);
|
|
848
|
+
|
|
849
|
+
// Keep row mutations inside the lock so replacement claims cannot interleave.
|
|
850
|
+
return write(tx);
|
|
851
|
+
});
|
|
850
852
|
}
|
|
851
853
|
|
|
852
854
|
/**
|
|
@@ -917,12 +919,55 @@ export type SyncAccountPatch = Partial<{
|
|
|
917
919
|
status: SyncAccountRow["status"];
|
|
918
920
|
lastError: string | null;
|
|
919
921
|
lastSyncedAt: number | null;
|
|
922
|
+
lastPushGeneration: number;
|
|
920
923
|
syncClaimId: string | null;
|
|
921
924
|
syncClaimedAt: number | null;
|
|
922
925
|
labels: CachedGmailLabel[];
|
|
923
926
|
labelsUpdatedAt: number;
|
|
924
927
|
}>;
|
|
925
928
|
|
|
929
|
+
export async function recordInboxPushInvalidation(
|
|
930
|
+
ownerEmail: string,
|
|
931
|
+
accountEmail: string,
|
|
932
|
+
): Promise<void> {
|
|
933
|
+
await getDb()
|
|
934
|
+
.insert(schema.mailInboxPushInvalidations)
|
|
935
|
+
.values({
|
|
936
|
+
id: rowId(ownerEmail, accountEmail),
|
|
937
|
+
ownerEmail: ownerEmail.toLowerCase(),
|
|
938
|
+
accountEmail: accountEmail.toLowerCase(),
|
|
939
|
+
generation: 1,
|
|
940
|
+
})
|
|
941
|
+
.onConflictDoUpdate({
|
|
942
|
+
target: schema.mailInboxPushInvalidations.id,
|
|
943
|
+
set: {
|
|
944
|
+
generation: sql`${schema.mailInboxPushInvalidations.generation} + 1`,
|
|
945
|
+
},
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
export async function readInboxPushGeneration(
|
|
950
|
+
ownerEmail: string,
|
|
951
|
+
accountEmail: string,
|
|
952
|
+
): Promise<number> {
|
|
953
|
+
const rows = await getDb()
|
|
954
|
+
.select({ generation: schema.mailInboxPushInvalidations.generation })
|
|
955
|
+
.from(schema.mailInboxPushInvalidations)
|
|
956
|
+
.where(
|
|
957
|
+
and(
|
|
958
|
+
eq(
|
|
959
|
+
schema.mailInboxPushInvalidations.ownerEmail,
|
|
960
|
+
ownerEmail.toLowerCase(),
|
|
961
|
+
),
|
|
962
|
+
eq(
|
|
963
|
+
schema.mailInboxPushInvalidations.accountEmail,
|
|
964
|
+
accountEmail.toLowerCase(),
|
|
965
|
+
),
|
|
966
|
+
),
|
|
967
|
+
);
|
|
968
|
+
return rows.reduce((total, row) => total + row.generation, 0);
|
|
969
|
+
}
|
|
970
|
+
|
|
926
971
|
/**
|
|
927
972
|
* Updates one sync-account row. When `opts.claimId` is given, the write is
|
|
928
973
|
* fenced with `AND sync_claim_id = ?` and the return value says whether a row
|
|
@@ -960,7 +1005,7 @@ export async function patchSyncAccount(
|
|
|
960
1005
|
* running against the reset row keeps its claim, and a later fenced write
|
|
961
1006
|
* from that same stale run would succeed and could restore the old
|
|
962
1007
|
* watermark. Clearing the claim here means that worker's next fenced write
|
|
963
|
-
* (via `patchSyncAccount`'s `opts.claimId` or `
|
|
1008
|
+
* (via `patchSyncAccount`'s `opts.claimId` or `withSyncClaim`) raises
|
|
964
1009
|
* {@link SyncClaimLostError} and stops it instead.
|
|
965
1010
|
*/
|
|
966
1011
|
export async function resetSyncAccountProgress(
|