@agent-native/core 0.84.0 → 0.84.2
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/core/CHANGELOG.md +13 -0
- package/corpus/core/docs/content/evals.mdx +34 -12
- package/corpus/core/docs/content/locales/ar-SA/evals.mdx +7 -0
- package/corpus/core/docs/content/locales/de-DE/evals.mdx +8 -0
- package/corpus/core/docs/content/locales/es-ES/evals.mdx +8 -0
- package/corpus/core/docs/content/locales/fr-FR/evals.mdx +8 -0
- package/corpus/core/docs/content/locales/hi-IN/evals.mdx +8 -0
- package/corpus/core/docs/content/locales/ja-JP/evals.mdx +7 -0
- package/corpus/core/docs/content/locales/ko-KR/evals.mdx +7 -0
- package/corpus/core/docs/content/locales/pt-BR/evals.mdx +8 -0
- package/corpus/core/docs/content/locales/zh-CN/evals.mdx +7 -0
- package/corpus/core/docs/content/locales/zh-TW/evals.mdx +7 -0
- package/corpus/core/docs/design/durable-agent-runs.md +34 -35
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/engine/builder-engine.ts +81 -9
- package/corpus/core/src/agent/run-manager.ts +8 -7
- package/corpus/core/src/cli/design-connect.ts +175 -0
- package/corpus/core/src/cli/skills.ts +5 -7
- package/corpus/core/src/client/AgentPanel.tsx +8 -0
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +4 -1
- package/corpus/core/src/client/agent-chat-adapter.ts +45 -15
- package/corpus/core/src/client/sse-event-processor.ts +31 -4
- package/corpus/core/src/eval/define-eval.ts +4 -1
- package/corpus/core/src/eval/report.ts +23 -8
- package/corpus/core/src/eval/runner.ts +41 -6
- package/corpus/core/src/eval/types.ts +11 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +15 -17
- package/corpus/templates/content/AGENTS.md +9 -2
- package/corpus/templates/content/actions/_database-row-batch.ts +230 -0
- package/corpus/templates/content/actions/_database-utils.ts +17 -9
- package/corpus/templates/content/actions/_notion-action-utils.ts +26 -0
- package/corpus/templates/content/actions/connect-notion-status.ts +13 -4
- package/corpus/templates/content/actions/create-and-link-notion-page.ts +23 -0
- package/corpus/templates/content/actions/delete-database-items.ts +55 -0
- package/corpus/templates/content/actions/delete-document.ts +4 -4
- package/corpus/templates/content/actions/disconnect-notion.ts +16 -0
- package/corpus/templates/content/actions/duplicate-database-item.ts +1 -1
- package/corpus/templates/content/actions/duplicate-database-items.ts +191 -0
- package/corpus/templates/content/actions/link-notion-page.ts +10 -7
- package/corpus/templates/content/actions/list-notion-links.ts +3 -4
- package/corpus/templates/content/actions/pull-notion-page.ts +7 -9
- package/corpus/templates/content/actions/push-notion-page.ts +7 -9
- package/corpus/templates/content/actions/refresh-notion-sync-status.ts +24 -0
- package/corpus/templates/content/actions/resolve-notion-sync-conflict.ts +23 -0
- package/corpus/templates/content/actions/search-notion-pages.ts +67 -0
- package/corpus/templates/content/actions/unlink-notion-page.ts +23 -0
- package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +27 -34
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +16 -13
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +10 -10
- package/corpus/templates/content/app/components/editor/NotionConflictBanner.tsx +1 -1
- package/corpus/templates/content/app/components/editor/NotionSyncBar.tsx +18 -9
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +27 -34
- package/corpus/templates/content/app/components/editor/database/navigation-state.ts +10 -4
- package/corpus/templates/content/app/components/sidebar/NotionButton.tsx +32 -17
- package/corpus/templates/content/app/hooks/use-content-database.ts +35 -0
- package/corpus/templates/content/app/hooks/use-notion.ts +108 -99
- package/corpus/templates/content/package.json +3 -0
- package/corpus/templates/content/parity/README.md +69 -0
- package/corpus/templates/content/parity/eval-scenarios.ts +118 -0
- package/corpus/templates/content/parity/exceptions.allowlist.ts +17 -0
- package/corpus/templates/content/parity/matrix.md +28 -0
- package/corpus/templates/content/parity/matrix.ts +606 -0
- package/corpus/templates/content/parity/matrix.types.ts +42 -0
- package/corpus/templates/content/parity/parity-evals.eval.ts +4 -0
- package/corpus/templates/content/parity/render-matrix.ts +90 -0
- package/corpus/templates/content/parity/scenario-to-eval.ts +57 -0
- package/corpus/templates/content/shared/api.ts +11 -0
- package/corpus/templates/design/actions/apply-motion-edit.ts +50 -18
- package/corpus/templates/design/actions/get-motion-timeline.ts +4 -14
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +156 -12
- package/corpus/templates/design/app/components/design/MotionDock.tsx +2 -3
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +217 -75
- package/corpus/templates/design/app/components/design/QuestionFlow.tsx +39 -39
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +558 -37
- package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +75 -3
- package/corpus/templates/design/app/components/design/types.ts +13 -0
- package/corpus/templates/design/app/components/layout/Layout.tsx +1 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +17 -0
- package/corpus/templates/design/app/i18n-data.ts +245 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +762 -153
- package/corpus/templates/design/changelog/2026-06-30-copying-or-dragging-screen-elements-onto-the-infinite-canvas.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-questions-now-use-tighter-editor-typography-and-contr.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-element-drags-can-be-cancelled-with-escape-before-they-commi.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-pending-visual-style-edits-now-warn-before-you-leave-the-edi.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-visual-style-drags-stay-live-while-pending-edits-can-be-appl.md +6 -0
- package/corpus/templates/design/changelog/2026-07-01-design-chat-no-longer-shows-a-redundant-context-tab-above-the-composer.md +6 -0
- package/corpus/templates/design/changelog/2026-07-01-motion-track-creation-no-longer-fails-in-local-editors.md +6 -0
- package/corpus/templates/design/server/plugins/db.ts +10 -0
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builder-engine.js +61 -10
- package/dist/agent/engine/builder-engine.js.map +1 -1
- package/dist/agent/run-manager.d.ts +8 -7
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +8 -7
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/cli/design-connect.d.ts +2 -0
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +140 -0
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +5 -7
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AgentPanel.d.ts +5 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +4 -4
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts +3 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +2 -2
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +37 -14
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +14 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +21 -5
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/eval/define-eval.d.ts.map +1 -1
- package/dist/eval/define-eval.js +2 -1
- package/dist/eval/define-eval.js.map +1 -1
- package/dist/eval/report.d.ts.map +1 -1
- package/dist/eval/report.js +19 -4
- package/dist/eval/report.js.map +1 -1
- package/dist/eval/runner.d.ts.map +1 -1
- package/dist/eval/runner.js +38 -6
- package/dist/eval/runner.js.map +1 -1
- package/dist/eval/types.d.ts +11 -0
- package/dist/eval/types.d.ts.map +1 -1
- package/dist/eval/types.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/observability/routes.d.ts +2 -2
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +15 -17
- package/docs/content/evals.mdx +34 -12
- package/docs/content/locales/ar-SA/evals.mdx +7 -0
- package/docs/content/locales/de-DE/evals.mdx +8 -0
- package/docs/content/locales/es-ES/evals.mdx +8 -0
- package/docs/content/locales/fr-FR/evals.mdx +8 -0
- package/docs/content/locales/hi-IN/evals.mdx +8 -0
- package/docs/content/locales/ja-JP/evals.mdx +7 -0
- package/docs/content/locales/ko-KR/evals.mdx +7 -0
- package/docs/content/locales/pt-BR/evals.mdx +8 -0
- package/docs/content/locales/zh-CN/evals.mdx +7 -0
- package/docs/content/locales/zh-TW/evals.mdx +7 -0
- package/docs/design/durable-agent-runs.md +34 -35
- package/package.json +1 -1
- package/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +15 -17
- package/corpus/templates/content/server/routes/api/notion/disconnect.post.ts +0 -12
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
|
|
4
|
+
import { parityMatrix } from "./matrix";
|
|
5
|
+
import type { ParityRow } from "./matrix.types";
|
|
6
|
+
|
|
7
|
+
function escapeCell(value: string) {
|
|
8
|
+
return value.replace(/\|/g, "\\|").replace(/\n/g, "<br>");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function cell(value: string | string[] | null | undefined) {
|
|
12
|
+
if (Array.isArray(value))
|
|
13
|
+
return escapeCell(value.length ? value.join(", ") : "-");
|
|
14
|
+
if (value === null || value === undefined || value === "") return "-";
|
|
15
|
+
return escapeCell(value);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const headers = [
|
|
19
|
+
"ID",
|
|
20
|
+
"Surface",
|
|
21
|
+
"User-visible action",
|
|
22
|
+
"Status",
|
|
23
|
+
"Actions",
|
|
24
|
+
"UI entrypoints",
|
|
25
|
+
"Durable effect",
|
|
26
|
+
"Exception / gap",
|
|
27
|
+
"Reliability risk",
|
|
28
|
+
"Spine priority",
|
|
29
|
+
"Test coverage",
|
|
30
|
+
"Coverage refs",
|
|
31
|
+
"Eval scenarios",
|
|
32
|
+
"Follow-up",
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
function renderRow(row: ParityRow) {
|
|
36
|
+
return [
|
|
37
|
+
row.id,
|
|
38
|
+
row.surface,
|
|
39
|
+
row.label,
|
|
40
|
+
row.status,
|
|
41
|
+
row.actions.length
|
|
42
|
+
? row.actions.map((action) => `\`${action}\``).join(", ")
|
|
43
|
+
: "-",
|
|
44
|
+
row.uiEntrypoints.map((entry) => `\`${entry}\``).join(", "),
|
|
45
|
+
row.durableEffect,
|
|
46
|
+
row.exception,
|
|
47
|
+
row.reliabilityRisk === "none" ? "-" : row.reliabilityRisk,
|
|
48
|
+
row.spinePriority,
|
|
49
|
+
row.testCoverage,
|
|
50
|
+
row.coverageRefs?.map((ref) => `\`${ref}\``).join(", "),
|
|
51
|
+
row.evalScenarioIds?.map((id) => `\`${id}\``).join(", "),
|
|
52
|
+
row.followUpPR,
|
|
53
|
+
].map(cell);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function renderTable(rows: string[][]) {
|
|
57
|
+
const widths = headers.map((header, index) =>
|
|
58
|
+
Math.max(header.length, ...rows.map((row) => row[index]?.length ?? 0)),
|
|
59
|
+
);
|
|
60
|
+
const renderCells = (cells: string[]) =>
|
|
61
|
+
`| ${cells.map((value, index) => value.padEnd(widths[index])).join(" | ")} |`;
|
|
62
|
+
return [
|
|
63
|
+
renderCells(headers),
|
|
64
|
+
renderCells(widths.map((width) => "-".repeat(width))),
|
|
65
|
+
...rows.map(renderCells),
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function renderParityMatrixMarkdown(rows: ParityRow[] = parityMatrix) {
|
|
70
|
+
const sortedRows = [...rows].sort((a, b) => a.id.localeCompare(b.id));
|
|
71
|
+
return [
|
|
72
|
+
"# Content Agent/UI Action Parity Matrix",
|
|
73
|
+
"",
|
|
74
|
+
"This generated matrix tracks whether high-value Content UI operations use the same action surface agents can call, or have an explicit exception. Edit `matrix.ts`, then regenerate this file.",
|
|
75
|
+
"",
|
|
76
|
+
...renderTable(sortedRows.map(renderRow)),
|
|
77
|
+
"",
|
|
78
|
+
].join("\n");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const isCli = process.argv[1] === fileURLToPath(import.meta.url);
|
|
82
|
+
|
|
83
|
+
if (isCli) {
|
|
84
|
+
const markdown = renderParityMatrixMarkdown();
|
|
85
|
+
if (process.argv.includes("--write")) {
|
|
86
|
+
writeFileSync(new URL("./matrix.md", import.meta.url), markdown);
|
|
87
|
+
} else {
|
|
88
|
+
process.stdout.write(markdown);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import {
|
|
2
|
+
contains,
|
|
3
|
+
createScorer,
|
|
4
|
+
defineEval,
|
|
5
|
+
type AgentRunOutput,
|
|
6
|
+
type Eval,
|
|
7
|
+
} from "@agent-native/core/eval";
|
|
8
|
+
|
|
9
|
+
import type { ParityEvalScenario } from "./eval-scenarios.ts";
|
|
10
|
+
|
|
11
|
+
function expectedToolScorer(expectedTools: string[]) {
|
|
12
|
+
return createScorer<AgentRunOutput, { used: string[]; missing: string[] }>({
|
|
13
|
+
name: "expected_tools",
|
|
14
|
+
analyze(run) {
|
|
15
|
+
const usedTools = new Set(run.toolCalls);
|
|
16
|
+
return {
|
|
17
|
+
used: expectedTools.filter((tool) => usedTools.has(tool)),
|
|
18
|
+
missing: expectedTools.filter((tool) => !usedTools.has(tool)),
|
|
19
|
+
};
|
|
20
|
+
},
|
|
21
|
+
generateScore({ missing }) {
|
|
22
|
+
return expectedTools.length === 0 || missing.length === 0 ? 1 : 0;
|
|
23
|
+
},
|
|
24
|
+
generateReason({ analysis: { used, missing } }) {
|
|
25
|
+
if (missing.length === 0) {
|
|
26
|
+
return `Agent called all expected tool(s): ${used.join(", ")}`;
|
|
27
|
+
}
|
|
28
|
+
return `Called expected tool(s): ${used.join(", ") || "none"}; missing: ${missing.join(", ")}`;
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function scenarioToEval(scenario: ParityEvalScenario): Eval {
|
|
34
|
+
const name = `content-parity:${scenario.id}`;
|
|
35
|
+
|
|
36
|
+
if (!process.env[scenario.gateEnv]) {
|
|
37
|
+
return defineEval({
|
|
38
|
+
name,
|
|
39
|
+
input: { prompt: scenario.prompt },
|
|
40
|
+
threshold: 1,
|
|
41
|
+
skipReason: `Skipped because ${scenario.gateEnv} is unset`,
|
|
42
|
+
scorers: [],
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return defineEval({
|
|
47
|
+
name,
|
|
48
|
+
input: { prompt: scenario.prompt },
|
|
49
|
+
threshold: 0.6,
|
|
50
|
+
scorers: [
|
|
51
|
+
contains(scenario.successSignals),
|
|
52
|
+
...(scenario.expectedTools?.length
|
|
53
|
+
? [expectedToolScorer(scenario.expectedTools)]
|
|
54
|
+
: []),
|
|
55
|
+
],
|
|
56
|
+
});
|
|
57
|
+
}
|
|
@@ -646,6 +646,10 @@ export interface ContentDatabaseResponse {
|
|
|
646
646
|
createdDocumentId?: string;
|
|
647
647
|
duplicatedItemId?: string;
|
|
648
648
|
duplicatedDocumentId?: string;
|
|
649
|
+
duplicatedItemIds?: string[];
|
|
650
|
+
duplicatedDocumentIds?: string[];
|
|
651
|
+
deletedItemIds?: string[];
|
|
652
|
+
deletedDocumentIds?: string[];
|
|
649
653
|
}
|
|
650
654
|
|
|
651
655
|
export interface ContentDatabaseUnavailableResponse {
|
|
@@ -701,6 +705,13 @@ export interface DuplicateDatabaseItemRequest {
|
|
|
701
705
|
title?: string;
|
|
702
706
|
}
|
|
703
707
|
|
|
708
|
+
export interface DatabaseItemsBatchRequest {
|
|
709
|
+
databaseId?: string;
|
|
710
|
+
documentId?: string;
|
|
711
|
+
itemIds?: string[];
|
|
712
|
+
documentIds?: string[];
|
|
713
|
+
}
|
|
714
|
+
|
|
704
715
|
export interface MoveDatabaseItemRequest {
|
|
705
716
|
itemId?: string;
|
|
706
717
|
documentId?: string;
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
getRequestOrgId,
|
|
21
21
|
getRequestUserEmail,
|
|
22
22
|
} from "@agent-native/core/server/request-context";
|
|
23
|
-
import {
|
|
23
|
+
import { assertAccess } from "@agent-native/core/sharing";
|
|
24
24
|
import { and, desc, eq } from "drizzle-orm";
|
|
25
25
|
import { nanoid } from "nanoid";
|
|
26
26
|
import { z } from "zod";
|
|
@@ -70,6 +70,33 @@ const trackSchema = z.object({
|
|
|
70
70
|
|
|
71
71
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
72
72
|
|
|
73
|
+
function nonEmptyString(value: unknown): string | null {
|
|
74
|
+
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function resolveMotionTimelineInsertOwnership(args: {
|
|
78
|
+
requestUserEmail?: string | null;
|
|
79
|
+
requestOrgId?: string | null;
|
|
80
|
+
designOwnerEmail?: unknown;
|
|
81
|
+
designOrgId?: unknown;
|
|
82
|
+
}): { ownerEmail: string; orgId: string | null } {
|
|
83
|
+
const ownerEmail =
|
|
84
|
+
nonEmptyString(args.requestUserEmail) ??
|
|
85
|
+
nonEmptyString(args.designOwnerEmail);
|
|
86
|
+
|
|
87
|
+
if (!ownerEmail) throw new Error("no authenticated user");
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
ownerEmail,
|
|
91
|
+
orgId:
|
|
92
|
+
nonEmptyString(args.requestOrgId) ?? nonEmptyString(args.designOrgId),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function canPatchManagedMotionCss(content: string): boolean {
|
|
97
|
+
return /<\s*(?:!doctype|[a-z][a-z0-9:-]*(?:\s|>|\/>))/i.test(content);
|
|
98
|
+
}
|
|
99
|
+
|
|
73
100
|
async function persistFileContent(
|
|
74
101
|
fileId: string,
|
|
75
102
|
designId: string,
|
|
@@ -179,16 +206,13 @@ export default defineAction({
|
|
|
179
206
|
includeContent,
|
|
180
207
|
currentContent: currentContentInput,
|
|
181
208
|
}) => {
|
|
182
|
-
await assertAccess("design", designId, "editor");
|
|
209
|
+
const access = await assertAccess("design", designId, "editor");
|
|
183
210
|
|
|
184
211
|
const db = getDb();
|
|
185
212
|
const now = new Date().toISOString();
|
|
186
213
|
|
|
187
214
|
// ── 1. Resolve the target design file ──────────────────────────────────
|
|
188
|
-
const conditions = [
|
|
189
|
-
accessFilter(schema.designs, schema.designShares),
|
|
190
|
-
eq(schema.designFiles.designId, designId),
|
|
191
|
-
];
|
|
215
|
+
const conditions = [eq(schema.designFiles.designId, designId)];
|
|
192
216
|
if (fileIdInput) {
|
|
193
217
|
conditions.push(eq(schema.designFiles.id, fileIdInput));
|
|
194
218
|
} else {
|
|
@@ -256,7 +280,10 @@ export default defineAction({
|
|
|
256
280
|
});
|
|
257
281
|
|
|
258
282
|
// ── 3. Inject the managed CSS block into the HTML ───────────────────────
|
|
259
|
-
const
|
|
283
|
+
const contentPatched = canPatchManagedMotionCss(currentContent);
|
|
284
|
+
const patchedContent = contentPatched
|
|
285
|
+
? injectManagedMotionCss(currentContent, css)
|
|
286
|
+
: currentContent;
|
|
260
287
|
const bytesBefore = currentContent.length;
|
|
261
288
|
const bytesAfter = patchedContent.length;
|
|
262
289
|
|
|
@@ -303,11 +330,18 @@ export default defineAction({
|
|
|
303
330
|
if (existingForSource) {
|
|
304
331
|
existingTimelineId = existingForSource.id;
|
|
305
332
|
} else {
|
|
306
|
-
// Insert new row — derive ownership from the request context
|
|
307
|
-
//
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
333
|
+
// Insert new row — derive ownership from the request context, falling
|
|
334
|
+
// back to the already-authorized design owner for local/public editor
|
|
335
|
+
// sessions that do not carry an authenticated request user.
|
|
336
|
+
const insertOwnership = resolveMotionTimelineInsertOwnership({
|
|
337
|
+
requestUserEmail: getRequestUserEmail(),
|
|
338
|
+
requestOrgId: getRequestOrgId(),
|
|
339
|
+
designOwnerEmail: (access.resource as { ownerEmail?: unknown })
|
|
340
|
+
.ownerEmail,
|
|
341
|
+
designOrgId: (access.resource as { orgId?: unknown }).orgId,
|
|
342
|
+
});
|
|
343
|
+
insertOwnerEmail = insertOwnership.ownerEmail;
|
|
344
|
+
insertOrgId = insertOwnership.orgId;
|
|
311
345
|
}
|
|
312
346
|
}
|
|
313
347
|
|
|
@@ -352,12 +386,9 @@ export default defineAction({
|
|
|
352
386
|
// Written after the row so a SQL failure here leaves the timeline row
|
|
353
387
|
// accurate (correct tracks + hash) and the stale HTML can be recompiled on
|
|
354
388
|
// the next apply-motion-edit call via compiledHash drift detection.
|
|
355
|
-
const updatedAt =
|
|
356
|
-
fileId,
|
|
357
|
-
|
|
358
|
-
patchedContent,
|
|
359
|
-
now,
|
|
360
|
-
);
|
|
389
|
+
const updatedAt = contentPatched
|
|
390
|
+
? await persistFileContent(fileId, designId, patchedContent, now)
|
|
391
|
+
: now;
|
|
361
392
|
|
|
362
393
|
return {
|
|
363
394
|
timelineId: resolvedTimelineId,
|
|
@@ -371,6 +402,7 @@ export default defineAction({
|
|
|
371
402
|
bytesAfter,
|
|
372
403
|
bytesDelta: bytesAfter - bytesBefore,
|
|
373
404
|
persisted: true,
|
|
405
|
+
contentPatched,
|
|
374
406
|
patchedContent: includeContent ? patchedContent : undefined,
|
|
375
407
|
};
|
|
376
408
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
2
|
import { getText, hasCollabState } from "@agent-native/core/collab";
|
|
3
|
-
import {
|
|
3
|
+
import { assertAccess } from "@agent-native/core/sharing";
|
|
4
4
|
import { and, desc, eq } from "drizzle-orm";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
|
|
@@ -67,13 +67,8 @@ async function readManagedCssForSource(args: {
|
|
|
67
67
|
content: schema.designFiles.content,
|
|
68
68
|
})
|
|
69
69
|
.from(schema.designFiles)
|
|
70
|
-
.innerJoin(
|
|
71
|
-
schema.designs,
|
|
72
|
-
eq(schema.designFiles.designId, schema.designs.id),
|
|
73
|
-
)
|
|
74
70
|
.where(
|
|
75
71
|
and(
|
|
76
|
-
accessFilter(schema.designs, schema.designShares),
|
|
77
72
|
eq(schema.designFiles.designId, args.designId),
|
|
78
73
|
eq(schema.designFiles.id, args.sourceRef),
|
|
79
74
|
),
|
|
@@ -124,12 +119,11 @@ export default defineAction({
|
|
|
124
119
|
),
|
|
125
120
|
}),
|
|
126
121
|
run: async ({ designId, timelineId, sourceRef }) => {
|
|
122
|
+
await assertAccess("design", designId, "viewer");
|
|
123
|
+
|
|
127
124
|
const db = getDb();
|
|
128
125
|
|
|
129
|
-
const conditions = [
|
|
130
|
-
accessFilter(schema.designs, schema.designShares),
|
|
131
|
-
eq(schema.motionTimeline.designId, designId),
|
|
132
|
-
];
|
|
126
|
+
const conditions = [eq(schema.motionTimeline.designId, designId)];
|
|
133
127
|
|
|
134
128
|
if (timelineId) {
|
|
135
129
|
conditions.push(eq(schema.motionTimeline.id, timelineId));
|
|
@@ -151,10 +145,6 @@ export default defineAction({
|
|
|
151
145
|
updatedAt: schema.motionTimeline.updatedAt,
|
|
152
146
|
})
|
|
153
147
|
.from(schema.motionTimeline)
|
|
154
|
-
.innerJoin(
|
|
155
|
-
schema.designs,
|
|
156
|
-
eq(schema.motionTimeline.designId, schema.designs.id),
|
|
157
|
-
)
|
|
158
148
|
.where(and(...conditions))
|
|
159
149
|
.orderBy(desc(schema.motionTimeline.updatedAt))
|
|
160
150
|
.limit(timelineId ? 1 : 100);
|
|
@@ -333,6 +333,13 @@ interface DesignCanvasProps {
|
|
|
333
333
|
contentOffsetY?: number;
|
|
334
334
|
};
|
|
335
335
|
boardSurface?: boolean;
|
|
336
|
+
/**
|
|
337
|
+
* Optional live document replacement channel. When paired with
|
|
338
|
+
* `runtimeReplacementKey`, this lets callers update iframe DOM through the
|
|
339
|
+
* editor bridge without changing `srcDoc` and reloading the iframe.
|
|
340
|
+
*/
|
|
341
|
+
runtimeReplacementContent?: string;
|
|
342
|
+
runtimeReplacementKey?: string;
|
|
336
343
|
embeddedFrameBackground?: string;
|
|
337
344
|
transparentBackground?: boolean;
|
|
338
345
|
editorChromeScaleX?: number;
|
|
@@ -367,6 +374,7 @@ interface DesignCanvasProps {
|
|
|
367
374
|
onElementDblClickText?: (info: ElementInfo) => void;
|
|
368
375
|
onIframeHotkey?: (event: IframeHotkeyPayload) => void;
|
|
369
376
|
onIframeContextMenu?: (event: IframeContextMenuPayload) => void;
|
|
377
|
+
onEditorDragStateChange?: (active: boolean) => void;
|
|
370
378
|
onVisualStructureChange?: (
|
|
371
379
|
selector: string,
|
|
372
380
|
anchorSelector: string,
|
|
@@ -376,6 +384,9 @@ interface DesignCanvasProps {
|
|
|
376
384
|
sourceId?: string;
|
|
377
385
|
anchorSourceId?: string;
|
|
378
386
|
requestId?: string;
|
|
387
|
+
dropMode?: "flow-insert" | "absolute-container";
|
|
388
|
+
sourceRect?: { x: number; y: number; width: number; height: number };
|
|
389
|
+
anchorRect?: { x: number; y: number; width: number; height: number };
|
|
379
390
|
},
|
|
380
391
|
) => boolean | void;
|
|
381
392
|
onVisualDuplicateChange?: (
|
|
@@ -571,6 +582,26 @@ function injectEmbeddedFrameStyle(content: string, style: string): string {
|
|
|
571
582
|
return `${style}${content}`;
|
|
572
583
|
}
|
|
573
584
|
|
|
585
|
+
export function getEmbeddedFrameDocumentContent(args: {
|
|
586
|
+
content: string;
|
|
587
|
+
embeddedFrameBackground?: string;
|
|
588
|
+
transparentBackground?: boolean;
|
|
589
|
+
contentOffsetX?: number;
|
|
590
|
+
contentOffsetY?: number;
|
|
591
|
+
}): string {
|
|
592
|
+
const frameStyle = [
|
|
593
|
+
getEmbeddedFrameBackgroundStyle({
|
|
594
|
+
embeddedFrameBackground: args.embeddedFrameBackground,
|
|
595
|
+
transparentBackground: args.transparentBackground,
|
|
596
|
+
}),
|
|
597
|
+
embeddedContentOffsetStyle(
|
|
598
|
+
args.contentOffsetX ?? 0,
|
|
599
|
+
args.contentOffsetY ?? 0,
|
|
600
|
+
),
|
|
601
|
+
].join("");
|
|
602
|
+
return injectEmbeddedFrameStyle(args.content, frameStyle);
|
|
603
|
+
}
|
|
604
|
+
|
|
574
605
|
export interface IframeHotkeyPayload {
|
|
575
606
|
key: string;
|
|
576
607
|
code: string;
|
|
@@ -602,6 +633,8 @@ export function DesignCanvas({
|
|
|
602
633
|
deviceFrame,
|
|
603
634
|
embeddedFrame,
|
|
604
635
|
boardSurface = false,
|
|
636
|
+
runtimeReplacementContent,
|
|
637
|
+
runtimeReplacementKey,
|
|
605
638
|
embeddedFrameBackground,
|
|
606
639
|
transparentBackground = false,
|
|
607
640
|
editorChromeScaleX = 1,
|
|
@@ -621,6 +654,7 @@ export function DesignCanvas({
|
|
|
621
654
|
onElementDblClickText,
|
|
622
655
|
onIframeHotkey,
|
|
623
656
|
onIframeContextMenu,
|
|
657
|
+
onEditorDragStateChange,
|
|
624
658
|
onVisualStructureChange,
|
|
625
659
|
onVisualDuplicateChange,
|
|
626
660
|
tweakValues,
|
|
@@ -652,6 +686,9 @@ export function DesignCanvas({
|
|
|
652
686
|
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
|
653
687
|
const zoomRef = useRef(zoom);
|
|
654
688
|
const previousContentKeyRef = useRef(contentKey);
|
|
689
|
+
const runtimeReplacementContentRef = useRef(runtimeReplacementContent);
|
|
690
|
+
const runtimeReplacementKeyRef = useRef(runtimeReplacementKey);
|
|
691
|
+
const lastRuntimeReplacementKeyRef = useRef(runtimeReplacementKey);
|
|
655
692
|
const [renderedContent, setRenderedContent] = useState(content);
|
|
656
693
|
const [annotationPins, setAnnotationPins] = useState<CanvasPin[]>([]);
|
|
657
694
|
const [pinSubmitSignal, setPinSubmitSignal] = useState(0);
|
|
@@ -707,6 +744,8 @@ export function DesignCanvas({
|
|
|
707
744
|
const waitingForEditableExternalSnapshot =
|
|
708
745
|
requiresEditableExternalSnapshot && !activeExternalSnapshotHtml;
|
|
709
746
|
zoomRef.current = zoom;
|
|
747
|
+
runtimeReplacementContentRef.current = runtimeReplacementContent;
|
|
748
|
+
runtimeReplacementKeyRef.current = runtimeReplacementKey;
|
|
710
749
|
|
|
711
750
|
useEffect(() => {
|
|
712
751
|
onExternalContentSnapshotRef.current = onExternalContentSnapshot;
|
|
@@ -819,6 +858,7 @@ export function DesignCanvas({
|
|
|
819
858
|
useEffect(() => {
|
|
820
859
|
if (previousContentKeyRef.current !== contentKey) {
|
|
821
860
|
previousContentKeyRef.current = contentKey;
|
|
861
|
+
lastRuntimeReplacementKeyRef.current = runtimeReplacementKey;
|
|
822
862
|
setRenderedContent(content);
|
|
823
863
|
}
|
|
824
864
|
// Same-screen visual edits are already applied optimistically inside the
|
|
@@ -826,7 +866,7 @@ export function DesignCanvas({
|
|
|
826
866
|
// reloads the iframe, flashes unstyled content, and drops selection. Only a
|
|
827
867
|
// content-key change (screen switch / explicit remount) should replace the
|
|
828
868
|
// iframe document here; the bridge replays inspector state after that load.
|
|
829
|
-
}, [content, contentKey]);
|
|
869
|
+
}, [content, contentKey, runtimeReplacementKey]);
|
|
830
870
|
|
|
831
871
|
usePinchZoom({
|
|
832
872
|
containerRef: scrollContainerRef,
|
|
@@ -879,6 +919,20 @@ export function DesignCanvas({
|
|
|
879
919
|
NAV_BRIDGE_SCRIPT +
|
|
880
920
|
embeddedWheelBridge +
|
|
881
921
|
editorChromeBridge;
|
|
922
|
+
const frameContent = getEmbeddedFrameDocumentContent({
|
|
923
|
+
content: iframeRenderContent,
|
|
924
|
+
embeddedFrameBackground,
|
|
925
|
+
transparentBackground,
|
|
926
|
+
contentOffsetX: embeddedFrame?.contentOffsetX ?? 0,
|
|
927
|
+
contentOffsetY: embeddedFrame?.contentOffsetY ?? 0,
|
|
928
|
+
});
|
|
929
|
+
if (frameContent.includes("</body>")) {
|
|
930
|
+
return frameContent.replace("</body>", bridgeToInject + "</body>"); // i18n-ignore generated iframe HTML injection
|
|
931
|
+
}
|
|
932
|
+
if (frameContent.includes("</html>")) {
|
|
933
|
+
return frameContent.replace("</html>", bridgeToInject + "</html>"); // i18n-ignore generated iframe HTML injection
|
|
934
|
+
}
|
|
935
|
+
// No body/html tags — wrap it
|
|
882
936
|
const frameStyle = [
|
|
883
937
|
getEmbeddedFrameBackgroundStyle({
|
|
884
938
|
embeddedFrameBackground,
|
|
@@ -889,17 +943,6 @@ export function DesignCanvas({
|
|
|
889
943
|
embeddedFrame?.contentOffsetY ?? 0,
|
|
890
944
|
),
|
|
891
945
|
].join("");
|
|
892
|
-
const frameContent = injectEmbeddedFrameStyle(
|
|
893
|
-
iframeRenderContent,
|
|
894
|
-
frameStyle,
|
|
895
|
-
);
|
|
896
|
-
if (frameContent.includes("</body>")) {
|
|
897
|
-
return frameContent.replace("</body>", bridgeToInject + "</body>"); // i18n-ignore generated iframe HTML injection
|
|
898
|
-
}
|
|
899
|
-
if (frameContent.includes("</html>")) {
|
|
900
|
-
return frameContent.replace("</html>", bridgeToInject + "</html>"); // i18n-ignore generated iframe HTML injection
|
|
901
|
-
}
|
|
902
|
-
// No body/html tags — wrap it
|
|
903
946
|
return `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">${frameStyle}</head><body>${iframeRenderContent}${bridgeToInject}</body></html>`;
|
|
904
947
|
// editorChromeScaleX/Y are intentionally NOT deps: they only seed the initial
|
|
905
948
|
// baked chrome scale. Live zoom updates flow through the set-editor-chrome-scale
|
|
@@ -968,6 +1011,10 @@ export function DesignCanvas({
|
|
|
968
1011
|
if (e.data.type === "element-hover") {
|
|
969
1012
|
onElementHover(e.data.payload);
|
|
970
1013
|
}
|
|
1014
|
+
if (e.data.type === "agent-native:editor-drag-state") {
|
|
1015
|
+
onEditorDragStateChange?.(Boolean(e.data.active));
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
971
1018
|
if (e.data.type === "visual-style-change") {
|
|
972
1019
|
const selector = String(e.data.selector || "");
|
|
973
1020
|
const styles =
|
|
@@ -1001,6 +1048,39 @@ export function DesignCanvas({
|
|
|
1001
1048
|
typeof e.data.anchorSourceId === "string"
|
|
1002
1049
|
? e.data.anchorSourceId
|
|
1003
1050
|
: undefined;
|
|
1051
|
+
const dropMode =
|
|
1052
|
+
e.data.dropMode === "flow-insert" ||
|
|
1053
|
+
e.data.dropMode === "absolute-container"
|
|
1054
|
+
? e.data.dropMode
|
|
1055
|
+
: undefined;
|
|
1056
|
+
const sourceRect =
|
|
1057
|
+
e.data.sourceRect &&
|
|
1058
|
+
typeof e.data.sourceRect === "object" &&
|
|
1059
|
+
Number.isFinite(e.data.sourceRect.x) &&
|
|
1060
|
+
Number.isFinite(e.data.sourceRect.y) &&
|
|
1061
|
+
Number.isFinite(e.data.sourceRect.width) &&
|
|
1062
|
+
Number.isFinite(e.data.sourceRect.height)
|
|
1063
|
+
? {
|
|
1064
|
+
x: Number(e.data.sourceRect.x),
|
|
1065
|
+
y: Number(e.data.sourceRect.y),
|
|
1066
|
+
width: Number(e.data.sourceRect.width),
|
|
1067
|
+
height: Number(e.data.sourceRect.height),
|
|
1068
|
+
}
|
|
1069
|
+
: undefined;
|
|
1070
|
+
const anchorRect =
|
|
1071
|
+
e.data.anchorRect &&
|
|
1072
|
+
typeof e.data.anchorRect === "object" &&
|
|
1073
|
+
Number.isFinite(e.data.anchorRect.x) &&
|
|
1074
|
+
Number.isFinite(e.data.anchorRect.y) &&
|
|
1075
|
+
Number.isFinite(e.data.anchorRect.width) &&
|
|
1076
|
+
Number.isFinite(e.data.anchorRect.height)
|
|
1077
|
+
? {
|
|
1078
|
+
x: Number(e.data.anchorRect.x),
|
|
1079
|
+
y: Number(e.data.anchorRect.y),
|
|
1080
|
+
width: Number(e.data.anchorRect.width),
|
|
1081
|
+
height: Number(e.data.anchorRect.height),
|
|
1082
|
+
}
|
|
1083
|
+
: undefined;
|
|
1004
1084
|
if (
|
|
1005
1085
|
(selector || sourceId) &&
|
|
1006
1086
|
(anchorSelector || anchorSourceId) &&
|
|
@@ -1017,6 +1097,9 @@ export function DesignCanvas({
|
|
|
1017
1097
|
requestId,
|
|
1018
1098
|
sourceId,
|
|
1019
1099
|
anchorSourceId,
|
|
1100
|
+
dropMode,
|
|
1101
|
+
sourceRect,
|
|
1102
|
+
anchorRect,
|
|
1020
1103
|
},
|
|
1021
1104
|
);
|
|
1022
1105
|
if (requestId) {
|
|
@@ -1209,6 +1292,7 @@ export function DesignCanvas({
|
|
|
1209
1292
|
onElementDblClickText,
|
|
1210
1293
|
onIframeHotkey,
|
|
1211
1294
|
onIframeContextMenu,
|
|
1295
|
+
onEditorDragStateChange,
|
|
1212
1296
|
onVisualStructureChange,
|
|
1213
1297
|
onVisualDuplicateChange,
|
|
1214
1298
|
onZoomChange,
|
|
@@ -1519,6 +1603,66 @@ export function DesignCanvas({
|
|
|
1519
1603
|
[],
|
|
1520
1604
|
);
|
|
1521
1605
|
|
|
1606
|
+
const replaceRuntimeContentInPlace = useCallback(
|
|
1607
|
+
(nextContent: string) => {
|
|
1608
|
+
if (externalPreviewUrl) return false;
|
|
1609
|
+
return replacePreviewContent(
|
|
1610
|
+
getEmbeddedFrameDocumentContent({
|
|
1611
|
+
content: nextContent,
|
|
1612
|
+
embeddedFrameBackground,
|
|
1613
|
+
transparentBackground,
|
|
1614
|
+
contentOffsetX: embeddedFrame?.contentOffsetX ?? 0,
|
|
1615
|
+
contentOffsetY: embeddedFrame?.contentOffsetY ?? 0,
|
|
1616
|
+
}),
|
|
1617
|
+
null,
|
|
1618
|
+
[],
|
|
1619
|
+
{ forceFullDocument: true },
|
|
1620
|
+
);
|
|
1621
|
+
},
|
|
1622
|
+
[
|
|
1623
|
+
embeddedFrame?.contentOffsetX,
|
|
1624
|
+
embeddedFrame?.contentOffsetY,
|
|
1625
|
+
embeddedFrameBackground,
|
|
1626
|
+
externalPreviewUrl,
|
|
1627
|
+
replacePreviewContent,
|
|
1628
|
+
transparentBackground,
|
|
1629
|
+
],
|
|
1630
|
+
);
|
|
1631
|
+
|
|
1632
|
+
useEffect(() => {
|
|
1633
|
+
if (
|
|
1634
|
+
runtimeReplacementKey === undefined ||
|
|
1635
|
+
runtimeReplacementContent === undefined ||
|
|
1636
|
+
lastRuntimeReplacementKeyRef.current === runtimeReplacementKey
|
|
1637
|
+
) {
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
if (replaceRuntimeContentInPlace(runtimeReplacementContent)) {
|
|
1641
|
+
lastRuntimeReplacementKeyRef.current = runtimeReplacementKey;
|
|
1642
|
+
}
|
|
1643
|
+
}, [
|
|
1644
|
+
replaceRuntimeContentInPlace,
|
|
1645
|
+
runtimeReplacementContent,
|
|
1646
|
+
runtimeReplacementKey,
|
|
1647
|
+
]);
|
|
1648
|
+
|
|
1649
|
+
const runtimeReplacementEnabled = runtimeReplacementKey !== undefined;
|
|
1650
|
+
useEffect(() => {
|
|
1651
|
+
if (!runtimeReplacementEnabled) return;
|
|
1652
|
+
const iframe = iframeRef.current;
|
|
1653
|
+
if (!iframe) return;
|
|
1654
|
+
const replaceLatestRuntimeContent = () => {
|
|
1655
|
+
const nextContent = runtimeReplacementContentRef.current;
|
|
1656
|
+
if (nextContent === undefined) return;
|
|
1657
|
+
if (replaceRuntimeContentInPlace(nextContent)) {
|
|
1658
|
+
lastRuntimeReplacementKeyRef.current = runtimeReplacementKeyRef.current;
|
|
1659
|
+
}
|
|
1660
|
+
};
|
|
1661
|
+
iframe.addEventListener("load", replaceLatestRuntimeContent);
|
|
1662
|
+
return () =>
|
|
1663
|
+
iframe.removeEventListener("load", replaceLatestRuntimeContent);
|
|
1664
|
+
}, [replaceRuntimeContentInPlace, runtimeReplacementEnabled]);
|
|
1665
|
+
|
|
1522
1666
|
const deleteRuntimeElement = useCallback(
|
|
1523
1667
|
(selector?: string | null, candidates?: string[]) => {
|
|
1524
1668
|
const iframe = iframeRef.current;
|
|
@@ -449,15 +449,14 @@ export function MotionDock({
|
|
|
449
449
|
|
|
450
450
|
{/* Dock toolbar */}
|
|
451
451
|
<div className="flex h-8 shrink-0 items-center gap-1 border-b border-border px-2">
|
|
452
|
-
{/* Collapse toggle. The
|
|
452
|
+
{/* Collapse toggle. The rail owns the visible Motion label. */}
|
|
453
453
|
<button
|
|
454
454
|
type="button"
|
|
455
|
-
className="-ml-1 flex
|
|
455
|
+
className="-ml-1 flex size-7 shrink-0 cursor-pointer items-center justify-center rounded-md text-muted-foreground outline-none transition-colors hover:bg-accent hover:text-foreground focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)]"
|
|
456
456
|
onClick={() => setOpen(false)}
|
|
457
457
|
aria-label="Collapse motion dock"
|
|
458
458
|
>
|
|
459
459
|
<IconChevronDown className="size-3.5" />
|
|
460
|
-
<span>Motion</span>
|
|
461
460
|
</button>
|
|
462
461
|
|
|
463
462
|
<>
|