@agent-native/core 0.119.6 → 0.120.1

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 (91) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +19 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/cli/index.ts +64 -3
  5. package/corpus/core/src/client/MultiTabAssistantChat.tsx +45 -0
  6. package/corpus/core/src/client/use-chat-models.ts +45 -0
  7. package/corpus/core/src/server/analytics.ts +15 -0
  8. package/corpus/core/src/server/auth.ts +17 -13
  9. package/corpus/core/src/server/better-auth-instance.ts +13 -1
  10. package/corpus/core/src/templates/default/package.json +1 -1
  11. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +1 -1
  12. package/corpus/templates/assets/changelog/2026-07-23-new-chat-aligns-with-sidebar-controls.md +6 -0
  13. package/corpus/templates/assets/package.json +1 -1
  14. package/corpus/templates/brain/package.json +1 -1
  15. package/corpus/templates/calendar/package.json +1 -1
  16. package/corpus/templates/clips/package.json +1 -1
  17. package/corpus/templates/content/actions/add-database-item.ts +1 -0
  18. package/corpus/templates/content/actions/set-document-property.ts +0 -4
  19. package/corpus/templates/content/app/components/editor/DocumentBlockFields.tsx +10 -1
  20. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +10 -4
  21. package/corpus/templates/content/app/components/editor/DocumentInfoPanel.tsx +5 -1
  22. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +6 -0
  23. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +52 -33
  24. package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +16 -4
  25. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +16 -7
  26. package/corpus/templates/content/app/hooks/use-create-page.ts +8 -3
  27. package/corpus/templates/content/app/hooks/use-document-properties.ts +1 -0
  28. package/corpus/templates/content/app/hooks/use-documents.ts +3 -1
  29. package/corpus/templates/content/app/lib/optimistic-document.ts +29 -0
  30. package/corpus/templates/content/changelog/2026-07-21-editing-a-database-property-no-longer-refreshes-unrelated-co.md +6 -0
  31. package/corpus/templates/content/changelog/2026-07-23-new-database-ready.md +6 -0
  32. package/corpus/templates/content/package.json +1 -1
  33. package/corpus/templates/content/shared/api.ts +1 -0
  34. package/corpus/templates/design/package.json +1 -1
  35. package/corpus/templates/dispatch/package.json +1 -1
  36. package/corpus/templates/forms/.agents/skills/form-publishing/SKILL.md +9 -3
  37. package/corpus/templates/forms/AGENTS.md +11 -1
  38. package/corpus/templates/forms/README.md +24 -1
  39. package/corpus/templates/forms/actions/update-form.ts +12 -5
  40. package/corpus/templates/forms/app/components/builder/FieldPropertiesPanel.tsx +199 -1
  41. package/corpus/templates/forms/app/i18n/en-US.ts +13 -1
  42. package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +35 -4
  43. package/corpus/templates/forms/app/pages/FormFillPage.tsx +7 -16
  44. package/corpus/templates/forms/changelog/2026-07-23-forms-can-branch-into-follow-up-questions-and-route-response.md +6 -0
  45. package/corpus/templates/forms/package.json +1 -1
  46. package/corpus/templates/forms/server/handlers/submissions.ts +22 -31
  47. package/corpus/templates/forms/server/lib/integrations.ts +11 -2
  48. package/corpus/templates/forms/server/lib/public-form-ssr.ts +26 -5
  49. package/corpus/templates/forms/server/lib/validate-fields.ts +48 -11
  50. package/corpus/templates/forms/server/plugins/agent-chat.ts +1 -0
  51. package/corpus/templates/forms/shared/conditional.ts +67 -0
  52. package/corpus/templates/macros/package.json +1 -1
  53. package/corpus/templates/mail/package.json +1 -1
  54. package/corpus/templates/slides/package.json +1 -1
  55. package/dist/cli/index.js +53 -2
  56. package/dist/cli/index.js.map +1 -1
  57. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  58. package/dist/client/MultiTabAssistantChat.js +35 -0
  59. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  60. package/dist/client/use-chat-models.d.ts +1 -0
  61. package/dist/client/use-chat-models.d.ts.map +1 -1
  62. package/dist/client/use-chat-models.js +33 -0
  63. package/dist/client/use-chat-models.js.map +1 -1
  64. package/dist/collab/struct-routes.d.ts +1 -1
  65. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  66. package/dist/notifications/routes.d.ts +3 -3
  67. package/dist/provider-api/actions/custom-provider-registration.d.ts +6 -6
  68. package/dist/provider-api/actions/provider-api.d.ts +9 -9
  69. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  70. package/dist/secrets/routes.d.ts +3 -3
  71. package/dist/server/analytics.d.ts +8 -0
  72. package/dist/server/analytics.d.ts.map +1 -1
  73. package/dist/server/analytics.js +16 -0
  74. package/dist/server/analytics.js.map +1 -1
  75. package/dist/server/auth.d.ts.map +1 -1
  76. package/dist/server/auth.js +5 -4
  77. package/dist/server/auth.js.map +1 -1
  78. package/dist/server/better-auth-instance.d.ts +8 -0
  79. package/dist/server/better-auth-instance.d.ts.map +1 -1
  80. package/dist/server/better-auth-instance.js +10 -1
  81. package/dist/server/better-auth-instance.js.map +1 -1
  82. package/dist/server/realtime-token.d.ts +1 -1
  83. package/dist/templates/default/package.json +1 -1
  84. package/package.json +1 -1
  85. package/src/cli/index.ts +64 -3
  86. package/src/client/MultiTabAssistantChat.tsx +45 -0
  87. package/src/client/use-chat-models.ts +45 -0
  88. package/src/server/analytics.ts +15 -0
  89. package/src/server/auth.ts +17 -13
  90. package/src/server/better-auth-instance.ts +13 -1
  91. package/src/templates/default/package.json +1 -1
package/corpus/README.md CHANGED
@@ -30,4 +30,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
30
30
 
31
31
  - core files: 1662
32
32
  - toolkit files: 159
33
- - template files: 6331
33
+ - template files: 6337
@@ -1,5 +1,24 @@
1
1
  # @agent-native/core
2
2
 
3
+ ## 0.120.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 5477352: Keep Dispatch's overview composer, full-page chat, and side chat on the same selected model.
8
+ - 5477352: Inject the configured Google Analytics tag into framework-owned login and signup pages.
9
+
10
+ ## 0.120.0
11
+
12
+ ### Minor Changes
13
+
14
+ - 20ebb96: `agent-native dev --inspect` (and `--inspect-brk`, optionally `=<port>`) now
15
+ attaches the Node inspector to **only** the Nitro API-server process, on a
16
+ single known port (default 9229). It selects Nitro's `node-process` dev runner
17
+ so the server is a real, attachable process, and injects `NODE_OPTIONS` through
18
+ a Vite preload that runs before Vite's own startup — so Vite, pnpm, and the CLI
19
+ are never inspected and there is exactly one debugger target. Set
20
+ `NITRO_DEV_RUNNER` yourself to override the runner.
21
+
3
22
  ## 0.119.6
4
23
 
5
24
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.119.6",
3
+ "version": "0.120.1",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -276,6 +276,22 @@ function findViteBin(): string {
276
276
  return findBinUpwards("vite") ?? "vite";
277
277
  }
278
278
 
279
+ function findViteJsEntry(): string | null {
280
+ try {
281
+ const require = createRequire(path.join(process.cwd(), "package.json"));
282
+ const pkgJsonPath = require.resolve("vite/package.json");
283
+ const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8")) as {
284
+ bin?: string | Record<string, string>;
285
+ };
286
+ const rel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.vite;
287
+ if (!rel) return null;
288
+ const entry = path.join(path.dirname(pkgJsonPath), rel);
289
+ return fs.existsSync(entry) ? entry : null;
290
+ } catch {
291
+ return null;
292
+ }
293
+ }
294
+
279
295
  function findTsxBin(): string {
280
296
  const localTsx = path.resolve("node_modules/.bin/tsx");
281
297
  if (fs.existsSync(localTsx)) return localTsx;
@@ -386,15 +402,31 @@ function isWorkspaceRoot(): boolean {
386
402
  }
387
403
  }
388
404
 
405
+ function extractNodeInspectFlag(args: string[]): {
406
+ inspectFlag: string | null;
407
+ rest: string[];
408
+ } {
409
+ const rest: string[] = [];
410
+ let inspectFlag: string | null = null;
411
+ for (const arg of args) {
412
+ if (/^--inspect(-brk)?(=.+)?$/.test(arg)) {
413
+ inspectFlag = arg;
414
+ continue;
415
+ }
416
+ rest.push(arg);
417
+ }
418
+ return { inspectFlag, rest };
419
+ }
420
+
389
421
  function run(
390
422
  cmd: string,
391
423
  cmdArgs: string[],
392
- opts?: { stdio?: "inherit" | "pipe" },
424
+ opts?: { stdio?: "inherit" | "pipe"; env?: NodeJS.ProcessEnv },
393
425
  ) {
394
426
  const child = spawn(cmd, cmdArgs, {
395
427
  stdio: opts?.stdio ?? "inherit",
396
428
  shell: process.platform === "win32",
397
- env: process.env,
429
+ env: opts?.env ?? process.env,
398
430
  });
399
431
  child.on("exit", (code) => process.exit(code ?? 0));
400
432
  // Forward signals to child so Cmd+C doesn't leave zombie processes holding ports
@@ -545,7 +577,36 @@ switch (command) {
545
577
  break;
546
578
  }
547
579
  const vite = findViteBin();
548
- run(vite, args);
580
+ const { inspectFlag, rest } = extractNodeInspectFlag(args);
581
+ if (!inspectFlag) {
582
+ run(vite, rest);
583
+ break;
584
+ }
585
+ const viteJsEntry = findViteJsEntry();
586
+ if (!viteJsEntry) {
587
+ console.warn(
588
+ "[agent-native] Could not resolve Vite's JS entry; starting dev " +
589
+ "server without the debugger.",
590
+ );
591
+ run(vite, rest);
592
+ break;
593
+ }
594
+ // Attach inspect flag to server process (not Vite or Nitro process)
595
+ const parsed = inspectFlag.match(/^--(inspect(?:-brk)?)(?:=(.+))?$/);
596
+ const kind = parsed?.[1] ?? "inspect";
597
+ const target = parsed?.[2] ?? "9229";
598
+ const directive = `--${kind}=${target}`;
599
+ const preload =
600
+ "data:text/javascript," +
601
+ encodeURIComponent(
602
+ `process.env.NODE_OPTIONS=((process.env.NODE_OPTIONS??"")+" ${directive}").trim();`,
603
+ );
604
+ const env = {
605
+ ...process.env,
606
+ NITRO_DEV_RUNNER: process.env.NITRO_DEV_RUNNER ?? "node-process",
607
+ };
608
+ console.log(`[agent-native] API server debugger listening on ${target}`);
609
+ run(process.execPath, ["--import", preload, viteJsEntry, ...rest], { env });
549
610
  break;
550
611
  }
551
612
 
@@ -56,6 +56,7 @@ import { isTrustedFrameMessage } from "./frame.js";
56
56
  import { RunStuckBanner } from "./RunStuckBanner.js";
57
57
  import { callAction } from "./use-action.js";
58
58
  import { useChangeVersion } from "./use-change-version.js";
59
+ import { CHAT_MODEL_SELECTION_CHANGED_EVENT } from "./use-chat-models.js";
59
60
  import {
60
61
  useChatThreads,
61
62
  type ChatThreadScope,
@@ -138,6 +139,13 @@ function writeStoredModelSelection(key: string, selection: ModelSelection) {
138
139
  if (typeof window === "undefined") return;
139
140
  try {
140
141
  window.localStorage.setItem(key, JSON.stringify(selection));
142
+ queueMicrotask(() => {
143
+ window.dispatchEvent(
144
+ new CustomEvent(CHAT_MODEL_SELECTION_CHANGED_EVENT, {
145
+ detail: { key },
146
+ }),
147
+ );
148
+ });
141
149
  } catch {}
142
150
  }
143
151
 
@@ -954,6 +962,43 @@ export function MultiTabAssistantChat({
954
962
  const bumpModelSelectionVersion = useCallback(() => {
955
963
  setModelSelectionVersion((version) => version + 1);
956
964
  }, []);
965
+
966
+ useEffect(() => {
967
+ if (typeof window === "undefined") return;
968
+
969
+ const syncPersistedSelection = (event?: Event) => {
970
+ const detail = (event as CustomEvent<{ key?: string }> | undefined)
971
+ ?.detail;
972
+ if (detail?.key && detail.key !== modelSelectionKey) return;
973
+
974
+ const next = readStoredModelSelection(modelSelectionKey);
975
+ if (!next) return;
976
+
977
+ const activeThreadId = activeThreadIdRef.current;
978
+ if (activeThreadId) {
979
+ threadModelRef.current.set(activeThreadId, next);
980
+ }
981
+ setPersistedModelSelection(next);
982
+ bumpModelSelectionVersion();
983
+ };
984
+ const handleStorage = (event: StorageEvent) => {
985
+ if (event.key === modelSelectionKey) syncPersistedSelection();
986
+ };
987
+
988
+ window.addEventListener(
989
+ CHAT_MODEL_SELECTION_CHANGED_EVENT,
990
+ syncPersistedSelection,
991
+ );
992
+ window.addEventListener("storage", handleStorage);
993
+ return () => {
994
+ window.removeEventListener(
995
+ CHAT_MODEL_SELECTION_CHANGED_EVENT,
996
+ syncPersistedSelection,
997
+ );
998
+ window.removeEventListener("storage", handleStorage);
999
+ };
1000
+ }, [bumpModelSelectionVersion, modelSelectionKey]);
1001
+
957
1002
  const postMessageSubmissionsDisabled = props.composerDisabled === true;
958
1003
 
959
1004
  const setContextInTab = useCallback(
@@ -42,6 +42,8 @@ interface Options {
42
42
  }
43
43
 
44
44
  const DEFAULT_STORAGE_KEY = "agent-native:chat-models:selection";
45
+ export const CHAT_MODEL_SELECTION_CHANGED_EVENT =
46
+ "agent-native:chat-model-selection-changed";
45
47
 
46
48
  interface PersistedSelection {
47
49
  model?: string;
@@ -63,6 +65,13 @@ function writePersisted(key: string | null, value: PersistedSelection) {
63
65
  if (!key || typeof window === "undefined") return;
64
66
  try {
65
67
  window.localStorage.setItem(key, JSON.stringify(value));
68
+ queueMicrotask(() => {
69
+ window.dispatchEvent(
70
+ new CustomEvent(CHAT_MODEL_SELECTION_CHANGED_EVENT, {
71
+ detail: { key },
72
+ }),
73
+ );
74
+ });
66
75
  } catch {}
67
76
  }
68
77
 
@@ -110,6 +119,42 @@ export function useChatModels({
110
119
  };
111
120
  }, [selectedEffort, selectedEngine, selectedModel]);
112
121
 
122
+ useEffect(() => {
123
+ if (!storageKey || typeof window === "undefined") return;
124
+
125
+ const syncPersistedSelection = (event?: Event) => {
126
+ const detail = (event as CustomEvent<{ key?: string }> | undefined)
127
+ ?.detail;
128
+ if (detail?.key && detail.key !== storageKey) return;
129
+
130
+ const next = readPersisted(storageKey);
131
+ if (!next.model) return;
132
+
133
+ hasExplicitSelectionRef.current = true;
134
+ setSelectedModel(next.model);
135
+ setSelectedEngine(next.engine ?? "");
136
+ setSelectedEffort(
137
+ resolveReasoningEffortSelection(next.model, next.effort),
138
+ );
139
+ };
140
+ const handleStorage = (event: StorageEvent) => {
141
+ if (event.key === storageKey) syncPersistedSelection();
142
+ };
143
+
144
+ window.addEventListener(
145
+ CHAT_MODEL_SELECTION_CHANGED_EVENT,
146
+ syncPersistedSelection,
147
+ );
148
+ window.addEventListener("storage", handleStorage);
149
+ return () => {
150
+ window.removeEventListener(
151
+ CHAT_MODEL_SELECTION_CHANGED_EVENT,
152
+ syncPersistedSelection,
153
+ );
154
+ window.removeEventListener("storage", handleStorage);
155
+ };
156
+ }, [storageKey]);
157
+
113
158
  const onModelChange = useCallback(
114
159
  (model: string, engine: string) => {
115
160
  hasExplicitSelectionRef.current = true;
@@ -74,6 +74,21 @@ function getGaScript(): string | null {
74
74
  );
75
75
  }
76
76
 
77
+ /**
78
+ * Add the configured analytics scripts to a complete HTML document.
79
+ *
80
+ * The normal app document is streamed through `wrapWithAnalytics`, but
81
+ * framework-owned documents such as `/signup` are returned as strings by the
82
+ * auth guard and need the same injection path.
83
+ */
84
+ export function injectAnalyticsIntoHtml(html: string): string {
85
+ const script = getGaScript();
86
+ if (!script) return html;
87
+ const headCloseIdx = html.indexOf("</head>");
88
+ if (headCloseIdx === -1) return html;
89
+ return html.slice(0, headCloseIdx) + script + html.slice(headCloseIdx);
90
+ }
91
+
77
92
  export function wrapWithAnalytics(body: ReadableStream): ReadableStream {
78
93
  const scripts = [getGaScript()].filter(Boolean).join("");
79
94
  if (!scripts) return body;
@@ -98,6 +98,7 @@ import {
98
98
  type WorkspaceAppAudience,
99
99
  } from "../shared/workspace-app-audience.js";
100
100
  import { isValidWorkspaceAppIdFormat } from "../shared/workspace-app-id.js";
101
+ import { injectAnalyticsIntoHtml } from "./analytics.js";
101
102
  import { signupAttributionFromCookieHeader } from "./attribution.js";
102
103
  import { getBetterAuth, getBetterAuthSync } from "./better-auth-instance.js";
103
104
  import type { BetterAuthConfig } from "./better-auth-instance.js";
@@ -1646,20 +1647,23 @@ function injectLoginSocialImageMeta(loginHtml: string, event: H3Event): string {
1646
1647
  }
1647
1648
 
1648
1649
  function loginHtmlResponse(loginHtml: string, event: H3Event): Response {
1649
- return new Response(injectLoginSocialImageMeta(loginHtml, event), {
1650
- status: 200,
1651
- headers: {
1652
- "Content-Type": "text/html; charset=utf-8",
1653
- // The sign-in document is part of the public server shell. Keep it on the
1654
- // same long-fresh/long-SWR CDN policy as React Router SSR so hosted
1655
- // template roots do not invoke origin just to render anonymous login UI.
1656
- // The login HTML is env-INDEPENDENT (a Google-only app always renders a
1657
- // working button), so a cached copy is never "wrong" never downgrade
1658
- // this to private/no-store.
1659
- ...DEFAULT_SSR_CACHE_HEADERS,
1660
- "X-Robots-Tag": "noindex, nofollow",
1650
+ return new Response(
1651
+ injectAnalyticsIntoHtml(injectLoginSocialImageMeta(loginHtml, event)),
1652
+ {
1653
+ status: 200,
1654
+ headers: {
1655
+ "Content-Type": "text/html; charset=utf-8",
1656
+ // The sign-in document is part of the public server shell. Keep it on the
1657
+ // same long-fresh/long-SWR CDN policy as React Router SSR so hosted
1658
+ // template roots do not invoke origin just to render anonymous login UI.
1659
+ // The login markup is env-INDEPENDENT (a Google-only app always renders
1660
+ // a working button); the analytics script is public build configuration,
1661
+ // not user/session state. Never downgrade this to private/no-store.
1662
+ ...DEFAULT_SSR_CACHE_HEADERS,
1663
+ "X-Robots-Tag": "noindex, nofollow",
1664
+ },
1661
1665
  },
1662
- });
1666
+ );
1663
1667
  }
1664
1668
 
1665
1669
  function isHtmlDocumentRequest(event: H3Event, pathname: string): boolean {
@@ -1109,6 +1109,18 @@ async function createBetterAuthInstance(
1109
1109
  return auth as unknown as BetterAuthInstance;
1110
1110
  }
1111
1111
 
1112
+ /**
1113
+ * Configure the local auth connection with the same write contention settings
1114
+ * as the shared app connection. Better Auth uses its own SQLite handle, so the
1115
+ * app connection's busy timeout does not protect first-run account creation.
1116
+ */
1117
+ export function configureLocalSqlite(sqlite: {
1118
+ pragma(statement: string): unknown;
1119
+ }): void {
1120
+ sqlite.pragma("busy_timeout = 10000");
1121
+ sqlite.pragma("journal_mode = WAL");
1122
+ }
1123
+
1112
1124
  async function buildDatabaseConfig(
1113
1125
  dialect: string,
1114
1126
  ): Promise<BetterAuthOptions["database"]> {
@@ -1182,7 +1194,7 @@ async function buildDatabaseConfig(
1182
1194
  const { default: Database } = await import("better-sqlite3");
1183
1195
  const filePath = url.replace(/^file:/, "");
1184
1196
  const sqlite = new Database(filePath);
1185
- sqlite.pragma("journal_mode = WAL");
1197
+ configureLocalSqlite(sqlite);
1186
1198
  const { drizzle } = await import("drizzle-orm/better-sqlite3");
1187
1199
  const db = drizzle(sqlite, { schema: sqliteAuthSchema });
1188
1200
  const { drizzleAdapter } = await import("better-auth/adapters/drizzle");
@@ -3,7 +3,7 @@
3
3
  "private": true,
4
4
  "type": "module",
5
5
  "scripts": {
6
- "dev": "agent-native dev",
6
+ "dev": "agent-native dev --inspect",
7
7
  "build": "agent-native build",
8
8
  "start": "agent-native start",
9
9
  "typecheck": "agent-native typecheck",
@@ -283,7 +283,7 @@ function AssetsChatsSection() {
283
283
  unpin: t("chat.unpinChat"),
284
284
  delete: t("chat.archiveChat"),
285
285
  }}
286
- className="min-w-0"
286
+ className="min-w-0 [&_.an-chat-history-rail__new-chat]:justify-start"
287
287
  />
288
288
  </div>
289
289
  );
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-23
4
+ ---
5
+
6
+ New chat is aligned consistently with the rest of the Assets sidebar.
@@ -4,7 +4,7 @@
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
7
- "dev": "agent-native dev",
7
+ "dev": "agent-native dev --inspect",
8
8
  "build": "agent-native build",
9
9
  "start": "agent-native start",
10
10
  "test": "vitest --run",
@@ -4,7 +4,7 @@
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
7
- "dev": "agent-native dev",
7
+ "dev": "agent-native dev --inspect",
8
8
  "build": "agent-native build",
9
9
  "start": "agent-native start",
10
10
  "test": "vitest --run --config vitest.config.ts",
@@ -4,7 +4,7 @@
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
7
- "dev": "agent-native dev",
7
+ "dev": "agent-native dev --inspect",
8
8
  "build": "agent-native build",
9
9
  "start": "agent-native start",
10
10
  "test": "vitest --run",
@@ -4,7 +4,7 @@
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
7
- "dev": "agent-native dev",
7
+ "dev": "agent-native dev --inspect",
8
8
  "build": "agent-native build",
9
9
  "start": "agent-native start",
10
10
  "typecheck": "agent-native typecheck",
@@ -217,6 +217,7 @@ export default defineAction({
217
217
  ...(await getContentDatabaseResponse(databaseId)),
218
218
  createdItemId: itemId,
219
219
  createdDocumentId: documentId,
220
+ createdDocumentUpdatedAt: now,
220
221
  };
221
222
  },
222
223
  });
@@ -1,5 +1,4 @@
1
1
  import { defineAction } from "@agent-native/core";
2
- import { writeAppState } from "@agent-native/core/application-state";
3
2
  import { assertAccess } from "@agent-native/core/sharing";
4
3
  import { and, eq } from "drizzle-orm";
5
4
  import { z } from "zod";
@@ -87,7 +86,6 @@ export default defineAction({
87
86
  now,
88
87
  });
89
88
  }
90
- await writeAppState("refresh-signal", { ts: Date.now() });
91
89
  return {
92
90
  documentId,
93
91
  databaseId: database.id,
@@ -133,8 +131,6 @@ export default defineAction({
133
131
  });
134
132
  }
135
133
 
136
- await writeAppState("refresh-signal", { ts: Date.now() });
137
-
138
134
  return {
139
135
  documentId,
140
136
  databaseId: database.id,
@@ -38,6 +38,7 @@ const BLOCK_FIELD_DRAG_THRESHOLD = 6;
38
38
 
39
39
  interface DocumentBlockFieldsProps {
40
40
  documentId: string;
41
+ databaseDocumentId: string;
41
42
  canEdit: boolean;
42
43
  /**
43
44
  * The fully-wired collaborative body editor for the primary "Content" field.
@@ -238,6 +239,7 @@ export function blockFieldsRenderState(args: {
238
239
  */
239
240
  export function DocumentBlockFields({
240
241
  documentId,
242
+ databaseDocumentId,
241
243
  canEdit,
242
244
  primaryEditor,
243
245
  }: DocumentBlockFieldsProps) {
@@ -303,6 +305,7 @@ export function DocumentBlockFields({
303
305
  // field while SAVING to another across an identity change.
304
306
  key={`${documentId}:${state.field.definition.id}`}
305
307
  documentId={documentId}
308
+ databaseDocumentId={databaseDocumentId}
306
309
  property={state.field}
307
310
  canEdit={canEdit}
308
311
  />
@@ -318,6 +321,7 @@ export function DocumentBlockFields({
318
321
  return (
319
322
  <MultiBlockFields
320
323
  documentId={documentId}
324
+ databaseDocumentId={databaseDocumentId}
321
325
  canEdit={canEdit}
322
326
  blockFields={state.fields}
323
327
  primaryEditor={primaryEditor}
@@ -329,12 +333,14 @@ export function DocumentBlockFields({
329
333
 
330
334
  function MultiBlockFields({
331
335
  documentId,
336
+ databaseDocumentId,
332
337
  canEdit,
333
338
  blockFields,
334
339
  primaryEditor,
335
340
  t,
336
341
  }: {
337
342
  documentId: string;
343
+ databaseDocumentId: string;
338
344
  canEdit: boolean;
339
345
  blockFields: DocumentProperty[];
340
346
  primaryEditor: ReactNode;
@@ -504,6 +510,7 @@ function MultiBlockFields({
504
510
  // doc's edits to the old field's closure.
505
511
  key={`${documentId}:${property.definition.id}`}
506
512
  documentId={documentId}
513
+ databaseDocumentId={databaseDocumentId}
507
514
  property={property}
508
515
  canEdit={canEdit}
509
516
  />
@@ -789,14 +796,16 @@ export function useBlockFieldEditor({
789
796
  */
790
797
  function AdditionalBlockEditor({
791
798
  documentId,
799
+ databaseDocumentId,
792
800
  property,
793
801
  canEdit,
794
802
  }: {
795
803
  documentId: string;
804
+ databaseDocumentId: string;
796
805
  property: DocumentProperty;
797
806
  canEdit: boolean;
798
807
  }) {
799
- const setProperty = useSetDocumentProperty(documentId);
808
+ const setProperty = useSetDocumentProperty(documentId, databaseDocumentId);
800
809
  const propertyId = property.definition.id;
801
810
  const initialContent =
802
811
  typeof property.value === "string" ? property.value : "";
@@ -80,6 +80,7 @@ import {
80
80
  canWriteLinkedLocalSource,
81
81
  writeDocumentToLinkedLocalSource,
82
82
  } from "@/lib/local-content-source-files";
83
+ import { isDatabaseChoicePending } from "@/lib/optimistic-document";
83
84
  import { cn } from "@/lib/utils";
84
85
 
85
86
  import {
@@ -1519,6 +1520,10 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
1519
1520
  );
1520
1521
  const defaultIconKind = documentEditorDefaultIconKind(document);
1521
1522
  const isDatabasePage = Boolean(document.database);
1523
+ const databaseChoicePending = isDatabaseChoicePending(
1524
+ document,
1525
+ createDatabase.isPending,
1526
+ );
1522
1527
  const showNewDocumentTypeChooser =
1523
1528
  canEdit &&
1524
1529
  !isLocalFileDocument &&
@@ -1791,12 +1796,10 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
1791
1796
  type="button"
1792
1797
  variant="outline"
1793
1798
  className="justify-start gap-2"
1794
- disabled={
1795
- !editorCanEdit || createDatabase.isPending
1796
- }
1799
+ disabled={!editorCanEdit || databaseChoicePending}
1797
1800
  onClick={() => void handleChooseDatabase()}
1798
1801
  >
1799
- {createDatabase.isPending ? (
1802
+ {databaseChoicePending ? (
1800
1803
  <IconLoader2 className="animate-spin" />
1801
1804
  ) : (
1802
1805
  <IconDatabase />
@@ -1867,6 +1870,9 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
1867
1870
  return (
1868
1871
  <DocumentBlockFields
1869
1872
  documentId={documentId}
1873
+ databaseDocumentId={
1874
+ document.databaseMembership.databaseDocumentId
1875
+ }
1870
1876
  canEdit={editorCanEdit}
1871
1877
  primaryEditor={primaryEditor}
1872
1878
  />
@@ -32,7 +32,11 @@ export function DocumentInfoPanel({
32
32
  onSave={onSaveDescription}
33
33
  />
34
34
  {document.databaseMembership && !isLocalFileDocument ? (
35
- <DocumentProperties documentId={document.id} canEdit={canEdit} />
35
+ <DocumentProperties
36
+ documentId={document.id}
37
+ databaseDocumentId={document.databaseMembership.databaseDocumentId}
38
+ canEdit={canEdit}
39
+ />
36
40
  ) : null}
37
41
  </div>
38
42
  );
@@ -165,6 +165,7 @@ function tWithFallback(
165
165
 
166
166
  interface DocumentPropertiesProps {
167
167
  documentId: string;
168
+ databaseDocumentId: string;
168
169
  canEdit: boolean;
169
170
  popoversPortalled?: boolean;
170
171
  }
@@ -796,6 +797,7 @@ function scalarPlaceholder(type: DocumentPropertyType, t: TFunction) {
796
797
 
797
798
  export function DocumentProperties({
798
799
  documentId,
800
+ databaseDocumentId,
799
801
  canEdit,
800
802
  popoversPortalled = true,
801
803
  }: DocumentPropertiesProps) {
@@ -826,6 +828,7 @@ export function DocumentProperties({
826
828
  key={property.definition.id}
827
829
  property={property}
828
830
  documentId={documentId}
831
+ databaseDocumentId={databaseDocumentId}
829
832
  canEdit={canEdit}
830
833
  popoversPortalled={popoversPortalled}
831
834
  t={t}
@@ -927,12 +930,14 @@ function HiddenPropertiesMenu({
927
930
  function PropertyRow({
928
931
  property,
929
932
  documentId,
933
+ databaseDocumentId,
930
934
  canEdit,
931
935
  popoversPortalled,
932
936
  t,
933
937
  }: {
934
938
  property: DocumentProperty;
935
939
  documentId: string;
940
+ databaseDocumentId: string;
936
941
  canEdit: boolean;
937
942
  popoversPortalled: boolean;
938
943
  t: TFunction;
@@ -978,6 +983,7 @@ function PropertyRow({
978
983
  <PropertyValuePopover
979
984
  property={property}
980
985
  documentId={documentId}
986
+ databaseDocumentId={databaseDocumentId}
981
987
  portalled={popoversPortalled}
982
988
  >
983
989
  {value}