@agent-native/core 0.114.12 → 0.114.13

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 (101) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +6 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/application-state/index.ts +3 -0
  5. package/corpus/core/src/application-state/script-helpers.ts +12 -1
  6. package/corpus/core/src/application-state/store.ts +216 -15
  7. package/corpus/core/src/db/client.ts +44 -1
  8. package/corpus/templates/clips/changelog/2026-07-21-live-dictation-text-now-appears-above-the-desktop-recording-.md +6 -0
  9. package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +14 -3
  10. package/corpus/templates/clips/desktop/src/overlays/flow-bar.tsx +25 -0
  11. package/corpus/templates/clips/desktop/src/styles.css +20 -0
  12. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +3 -4
  13. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +11 -4
  14. package/corpus/templates/design/AGENTS.md +7 -3
  15. package/corpus/templates/design/actions/begin-node-rewrite-request.ts +78 -0
  16. package/corpus/templates/design/actions/cancel-node-rewrite-request.ts +17 -7
  17. package/corpus/templates/design/actions/propose-node-rewrite.ts +26 -18
  18. package/corpus/templates/design/actions/resolve-node-rewrite.ts +67 -19
  19. package/corpus/templates/design/app/components/visual-editor/NodeRewriteProposal.tsx +1 -4
  20. package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +2 -6
  21. package/corpus/templates/design/shared/node-rewrite.ts +32 -0
  22. package/corpus/templates/forms/app/components/layout/Sidebar.tsx +6 -3
  23. package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +11 -18
  24. package/corpus/templates/forms/app/pages/FormsListPage.tsx +9 -5
  25. package/corpus/templates/slides/app/context/DeckContext.tsx +18 -0
  26. package/corpus/templates/slides/changelog/2026-07-21-decks-list-now-refreshes-when-you-switch-organizations.md +6 -0
  27. package/corpus/templates/tasks/app/components/custom-fields/FieldCreateBar.tsx +13 -19
  28. package/corpus/templates/tasks/app/components/custom-fields/FieldListRow.tsx +27 -22
  29. package/corpus/templates/tasks/app/components/custom-fields/FieldsList.tsx +26 -22
  30. package/corpus/templates/tasks/app/components/custom-fields/FieldsListSkeleton.tsx +4 -1
  31. package/corpus/templates/tasks/app/components/custom-fields/FieldsPage.tsx +4 -2
  32. package/corpus/templates/tasks/app/components/custom-fields/editor/FieldEditorSidebar.tsx +12 -3
  33. package/corpus/templates/tasks/app/components/custom-fields/editor/FieldTitleSection.tsx +6 -3
  34. package/corpus/templates/tasks/app/components/custom-fields/editor/config/CurrencyConfigControl.tsx +5 -2
  35. package/corpus/templates/tasks/app/components/custom-fields/editor/config/NumberConfigControl.tsx +5 -2
  36. package/corpus/templates/tasks/app/components/custom-fields/editor/config/PercentConfigControl.tsx +4 -1
  37. package/corpus/templates/tasks/app/components/custom-fields/editor/config/SelectConfigControl.tsx +17 -7
  38. package/corpus/templates/tasks/app/components/custom-fields/editor/config/select-colors.ts +37 -9
  39. package/corpus/templates/tasks/app/components/custom-fields/field-types.ts +23 -0
  40. package/corpus/templates/tasks/app/components/inbox/InboxList.tsx +24 -16
  41. package/corpus/templates/tasks/app/components/inbox/InboxListPage.tsx +5 -3
  42. package/corpus/templates/tasks/app/components/inbox/InboxListRow.tsx +7 -5
  43. package/corpus/templates/tasks/app/components/layout/Header.tsx +10 -10
  44. package/corpus/templates/tasks/app/components/layout/Layout.tsx +11 -7
  45. package/corpus/templates/tasks/app/components/layout/Sidebar.tsx +17 -10
  46. package/corpus/templates/tasks/app/components/shared/AddListItemInput.tsx +14 -8
  47. package/corpus/templates/tasks/app/components/shared/AlertDialog.tsx +6 -2
  48. package/corpus/templates/tasks/app/components/shared/BulkDeleteDialog.tsx +13 -6
  49. package/corpus/templates/tasks/app/components/shared/ChipSelect.tsx +12 -5
  50. package/corpus/templates/tasks/app/components/shared/DeleteItemDialog.tsx +6 -6
  51. package/corpus/templates/tasks/app/components/shared/ListViewHeader.tsx +3 -1
  52. package/corpus/templates/tasks/app/components/shared/SidePanel.tsx +5 -2
  53. package/corpus/templates/tasks/app/components/shared/list/ListRowDragHandle.tsx +3 -1
  54. package/corpus/templates/tasks/app/components/shared/selection/ListSelectionBar.tsx +46 -20
  55. package/corpus/templates/tasks/app/components/shared/selection/ListSelectionHeaderToggle.tsx +3 -1
  56. package/corpus/templates/tasks/app/components/shared/selection/ListSelectionToolbar.tsx +10 -6
  57. package/corpus/templates/tasks/app/components/tasks/TaskList.tsx +24 -13
  58. package/corpus/templates/tasks/app/components/tasks/TaskListHeaderRow.tsx +4 -1
  59. package/corpus/templates/tasks/app/components/tasks/TaskListPage.tsx +5 -3
  60. package/corpus/templates/tasks/app/components/tasks/TaskListRow.tsx +15 -6
  61. package/corpus/templates/tasks/app/components/tasks/fields/TaskFieldsSidebar.tsx +11 -6
  62. package/corpus/templates/tasks/app/components/tasks/fields/TaskTitleSection.tsx +8 -3
  63. package/corpus/templates/tasks/app/components/tasks/fields/controls/RichTextValueControl.tsx +12 -6
  64. package/corpus/templates/tasks/app/components/tasks/fields/controls/SingleSelectValueControl.tsx +6 -1
  65. package/corpus/templates/tasks/app/i18n/en-US.ts +209 -0
  66. package/corpus/templates/tasks/app/i18n/index.ts +34 -0
  67. package/corpus/templates/tasks/app/root.tsx +34 -2
  68. package/corpus/templates/tasks/app/routes/extensions.$id.tsx +2 -1
  69. package/corpus/templates/tasks/app/routes/extensions._index.tsx +2 -1
  70. package/corpus/templates/tasks/app/routes/fields.tsx +2 -1
  71. package/corpus/templates/tasks/app/routes/inbox.tsx +2 -1
  72. package/corpus/templates/tasks/app/routes/tasks.tsx +2 -1
  73. package/corpus/templates/tasks/app/routes/team.tsx +6 -3
  74. package/corpus/templates/tasks/changelog/2026-07-20-tasks-is-now-available-in-10-additional-languages.md +6 -0
  75. package/dist/application-state/index.d.ts +2 -2
  76. package/dist/application-state/index.d.ts.map +1 -1
  77. package/dist/application-state/index.js +2 -2
  78. package/dist/application-state/index.js.map +1 -1
  79. package/dist/application-state/script-helpers.d.ts +3 -1
  80. package/dist/application-state/script-helpers.d.ts.map +1 -1
  81. package/dist/application-state/script-helpers.js +7 -1
  82. package/dist/application-state/script-helpers.js.map +1 -1
  83. package/dist/application-state/store.d.ts +7 -1
  84. package/dist/application-state/store.d.ts.map +1 -1
  85. package/dist/application-state/store.js +143 -17
  86. package/dist/application-state/store.js.map +1 -1
  87. package/dist/collab/routes.d.ts +1 -1
  88. package/dist/collab/struct-routes.d.ts +1 -1
  89. package/dist/db/client.d.ts +7 -0
  90. package/dist/db/client.d.ts.map +1 -1
  91. package/dist/db/client.js +39 -1
  92. package/dist/db/client.js.map +1 -1
  93. package/dist/notifications/routes.d.ts +1 -1
  94. package/dist/observability/routes.d.ts +3 -3
  95. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  96. package/dist/server/transcribe-voice.d.ts +1 -1
  97. package/package.json +1 -1
  98. package/src/application-state/index.ts +3 -0
  99. package/src/application-state/script-helpers.ts +12 -1
  100. package/src/application-state/store.ts +216 -15
  101. package/src/db/client.ts +44 -1
@@ -0,0 +1,78 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import {
3
+ compareAndSetAppState,
4
+ readAppState,
5
+ } from "@agent-native/core/application-state";
6
+ import { assertAccess } from "@agent-native/core/sharing";
7
+ import { z } from "zod";
8
+
9
+ import {
10
+ designRepromptPendingStateKey,
11
+ isNodeRewriteResolutionClaim,
12
+ isPendingDesignReprompt,
13
+ type PendingDesignReprompt,
14
+ } from "../shared/node-rewrite.js";
15
+
16
+ const targetSchema = z
17
+ .object({
18
+ nodeId: z.string().min(1).optional(),
19
+ selector: z.string().min(1).optional(),
20
+ })
21
+ .refine((target) => target.nodeId || target.selector, {
22
+ message: "target.nodeId or target.selector is required",
23
+ });
24
+
25
+ export default defineAction({
26
+ agentTool: false,
27
+ description:
28
+ "Atomically starts or supersedes one pending node rewrite unless its current proposal is being accepted.",
29
+ schema: z.object({
30
+ repromptId: z.string().min(1),
31
+ designId: z.string().min(1),
32
+ fileId: z.string().min(1),
33
+ target: targetSchema,
34
+ baseVersionHash: z.string().min(1),
35
+ instruction: z.string().trim().min(1),
36
+ createdAt: z.string().min(1),
37
+ priorProposalId: z.string().min(1).optional(),
38
+ priorRepromptId: z.string().min(1).optional(),
39
+ }),
40
+ run: async (request) => {
41
+ await assertAccess("design", request.designId, "editor");
42
+ const pendingKey = designRepromptPendingStateKey(
43
+ request.designId,
44
+ request.fileId,
45
+ );
46
+ const current = await readAppState(pendingKey);
47
+ if (isNodeRewriteResolutionClaim(current)) {
48
+ throw new Error(
49
+ "This candidate is currently being accepted. Wait for it to finish before regenerating.",
50
+ );
51
+ }
52
+ if (
53
+ request.priorRepromptId &&
54
+ (!isPendingDesignReprompt(current) ||
55
+ current.repromptId !== request.priorRepromptId)
56
+ ) {
57
+ throw new Error(
58
+ "The candidates changed before refinement started. Review the latest candidates instead.",
59
+ );
60
+ }
61
+
62
+ const pending: PendingDesignReprompt = {
63
+ ...request,
64
+ status: "pending",
65
+ };
66
+ const started = await compareAndSetAppState(
67
+ pendingKey,
68
+ current,
69
+ pending as unknown as Record<string, unknown>,
70
+ );
71
+ if (!started) {
72
+ throw new Error(
73
+ "The regeneration request changed concurrently. Try again with the latest candidates.",
74
+ );
75
+ }
76
+ return { started: true, repromptId: request.repromptId };
77
+ },
78
+ });
@@ -1,6 +1,7 @@
1
1
  import { defineAction } from "@agent-native/core";
2
2
  import {
3
3
  compareAndSetAppState,
4
+ compareAndSetManyAppState,
4
5
  readAppState,
5
6
  } from "@agent-native/core/application-state";
6
7
  import { assertAccess } from "@agent-native/core/sharing";
@@ -39,15 +40,24 @@ export default defineAction({
39
40
  repromptId,
40
41
  );
41
42
  const proposal = await readAppState(proposalKey);
42
- const [pendingCancelled, proposalCancelled] = await Promise.all([
43
- compareAndSetAppState(pendingKey, pending, null),
44
- isNodeRewriteProposal(proposal)
45
- ? compareAndSetAppState(proposalKey, proposal, null)
46
- : Promise.resolve(false),
47
- ]);
43
+ const proposalCancelled = isNodeRewriteProposal(proposal);
44
+ const pendingCancelled = proposalCancelled
45
+ ? await compareAndSetManyAppState([
46
+ {
47
+ key: pendingKey,
48
+ expectedValue: pending,
49
+ nextValue: null,
50
+ },
51
+ {
52
+ key: proposalKey,
53
+ expectedValue: proposal,
54
+ nextValue: null,
55
+ },
56
+ ])
57
+ : await compareAndSetAppState(pendingKey, pending, null);
48
58
  return {
49
59
  cancelled: pendingCancelled,
50
- proposalCancelled,
60
+ proposalCancelled: proposalCancelled && pendingCancelled,
51
61
  superseded: !pendingCancelled,
52
62
  };
53
63
  },
@@ -1,8 +1,8 @@
1
1
  import { defineAction } from "@agent-native/core";
2
2
  import {
3
- compareAndSetAppState,
3
+ compareAndSetManyAppState,
4
4
  readAppState,
5
- writeAppState,
5
+ type AppStateCompareAndSetOperation,
6
6
  } from "@agent-native/core/application-state";
7
7
  import { accessFilter, assertAccess } from "@agent-native/core/sharing";
8
8
  import { and, eq } from "drizzle-orm";
@@ -68,6 +68,7 @@ const variantSchema = z.object({
68
68
  });
69
69
 
70
70
  const pendingRepromptSchema = z.object({
71
+ status: z.literal("pending").optional(),
71
72
  repromptId: z.string().min(1),
72
73
  designId: z.string().min(1),
73
74
  fileId: z.string().min(1),
@@ -241,21 +242,18 @@ export default defineAction({
241
242
  file.id,
242
243
  repromptId,
243
244
  );
244
- await writeAppState(
245
- proposalKey,
246
- proposal as unknown as Record<string, unknown>,
247
- );
248
- const currentPending = await readAppState(pendingKey);
249
- if (currentPending?.repromptId !== repromptId) {
250
- await compareAndSetAppState(
251
- proposalKey,
252
- proposal as unknown as Record<string, unknown>,
253
- null,
254
- );
255
- throw new Error(
256
- "This regeneration was superseded by a newer request before its candidates were published.",
257
- );
258
- }
245
+ const publishOperations: AppStateCompareAndSetOperation[] = [
246
+ {
247
+ key: pendingKey,
248
+ expectedValue: pending as unknown as Record<string, unknown>,
249
+ nextValue: pending as unknown as Record<string, unknown>,
250
+ },
251
+ {
252
+ key: proposalKey,
253
+ expectedValue: null,
254
+ nextValue: proposal as unknown as Record<string, unknown>,
255
+ },
256
+ ];
259
257
  if (pending.priorProposalId && pending.priorRepromptId) {
260
258
  const priorProposalKey = designRepromptProposalStateKey(
261
259
  file.designId,
@@ -264,9 +262,19 @@ export default defineAction({
264
262
  );
265
263
  const priorProposal = await readAppState(priorProposalKey);
266
264
  if (priorProposal?.proposalId === pending.priorProposalId) {
267
- await compareAndSetAppState(priorProposalKey, priorProposal, null);
265
+ publishOperations.push({
266
+ key: priorProposalKey,
267
+ expectedValue: priorProposal,
268
+ nextValue: null,
269
+ });
268
270
  }
269
271
  }
272
+ const published = await compareAndSetManyAppState(publishOperations);
273
+ if (!published) {
274
+ throw new Error(
275
+ "This regeneration was superseded by a newer request before its candidates were published.",
276
+ );
277
+ }
270
278
 
271
279
  const bridgeMessages: NodeHtmlPreviewBridgeMessage[] = [
272
280
  {
@@ -1,11 +1,13 @@
1
1
  import { defineAction } from "@agent-native/core";
2
2
  import {
3
3
  compareAndSetAppState,
4
+ compareAndSetManyAppState,
4
5
  listAppState,
5
6
  readAppState,
6
7
  } from "@agent-native/core/application-state";
7
8
  import { accessFilter, assertAccess } from "@agent-native/core/sharing";
8
9
  import { and, eq } from "drizzle-orm";
10
+ import { nanoid } from "nanoid";
9
11
  import { z } from "zod";
10
12
 
11
13
  import { getDb, schema } from "../server/db/index.js";
@@ -22,6 +24,7 @@ import {
22
24
  isPendingDesignReprompt,
23
25
  spliceNodeRewriteVariant,
24
26
  type NodeHtmlPreviewBridgeMessage,
27
+ type NodeRewriteResolutionClaim,
25
28
  type NodeRewriteProposal,
26
29
  } from "../shared/node-rewrite.js";
27
30
  import { designSourceTypeFromData } from "../shared/source-mode.js";
@@ -142,22 +145,19 @@ async function clearProposalState(
142
145
  proposalKey: string,
143
146
  proposal: NodeRewriteProposal,
144
147
  pending: Record<string, unknown>,
145
- ): Promise<void> {
148
+ ): Promise<boolean> {
146
149
  const pendingKey = designRepromptPendingStateKey(
147
150
  proposal.designId,
148
151
  proposal.fileId,
149
152
  );
150
- const [proposalCleared] = await Promise.all([
151
- compareAndSetAppState(
152
- proposalKey,
153
- proposal as unknown as Record<string, unknown>,
154
- null,
155
- ),
156
- compareAndSetAppState(pendingKey, pending, null),
153
+ return compareAndSetManyAppState([
154
+ {
155
+ key: proposalKey,
156
+ expectedValue: proposal as unknown as Record<string, unknown>,
157
+ nextValue: null,
158
+ },
159
+ { key: pendingKey, expectedValue: pending, nextValue: null },
157
160
  ]);
158
- if (!proposalCleared) {
159
- throw new Error("Node rewrite proposal changed while it was resolving.");
160
- }
161
161
  }
162
162
 
163
163
  export default defineAction({
@@ -195,7 +195,16 @@ export default defineAction({
195
195
 
196
196
  if (resolution === "reject") {
197
197
  await assertAccess("design", proposal.designId, "editor");
198
- await clearProposalState(proposalKey, proposal, pending);
198
+ const cleanupComplete = await clearProposalState(
199
+ proposalKey,
200
+ proposal,
201
+ pending,
202
+ );
203
+ if (!cleanupComplete) {
204
+ throw new Error(
205
+ "The proposal changed while it was being rejected. Review the latest candidates instead.",
206
+ );
207
+ }
199
208
  return {
200
209
  proposalId,
201
210
  repromptId: proposal.repromptId,
@@ -234,13 +243,51 @@ export default defineAction({
234
243
  source,
235
244
  fileType: file.fileType,
236
245
  });
237
- const write = await writeInlineSourceFile({
238
- designId: file.designId,
239
- file,
240
- content: rewrite.content,
241
- expectedVersionHash: proposal.baseVersionHash,
242
- });
243
- await clearProposalState(proposalKey, proposal, pending);
246
+ const claim: NodeRewriteResolutionClaim = {
247
+ ...pending,
248
+ status: "resolving",
249
+ claimId: `node-rewrite-claim-${nanoid()}`,
250
+ proposalId,
251
+ resolution: "accept",
252
+ };
253
+ const claimValue = claim as unknown as Record<string, unknown>;
254
+ const claimed = await compareAndSetAppState(
255
+ pendingKey,
256
+ pending,
257
+ claimValue,
258
+ );
259
+ if (!claimed) {
260
+ throw new Error(
261
+ "A newer regeneration request replaced this proposal before it could be accepted.",
262
+ );
263
+ }
264
+
265
+ let write;
266
+ try {
267
+ write = await writeInlineSourceFile({
268
+ designId: file.designId,
269
+ file,
270
+ content: rewrite.content,
271
+ expectedVersionHash: proposal.baseVersionHash,
272
+ });
273
+ } catch (error) {
274
+ const released = await compareAndSetAppState(
275
+ pendingKey,
276
+ claimValue,
277
+ pending,
278
+ );
279
+ if (!released) {
280
+ throw new Error(
281
+ "The design write failed and its regeneration reservation could not be restored.",
282
+ );
283
+ }
284
+ throw error;
285
+ }
286
+ const cleanupComplete = await clearProposalState(
287
+ proposalKey,
288
+ proposal,
289
+ claimValue,
290
+ );
244
291
 
245
292
  return {
246
293
  proposalId,
@@ -252,6 +299,7 @@ export default defineAction({
252
299
  designId: file.designId,
253
300
  fileId: file.id,
254
301
  versionHash: write.versionHash,
302
+ cleanupComplete,
255
303
  };
256
304
  },
257
305
  });
@@ -1,12 +1,10 @@
1
1
  import {
2
2
  callAction,
3
- setClientAppState,
4
3
  useActionMutation,
5
4
  useChangeVersion,
6
5
  } from "@agent-native/core/client/hooks";
7
6
  import { useT } from "@agent-native/core/client/i18n";
8
7
  import {
9
- designRepromptPendingStateKey,
10
8
  isNodeRewriteProposal,
11
9
  type NodeHtmlPreviewBridgeMessage,
12
10
  type NodeRewriteProposal,
@@ -426,7 +424,6 @@ export function NodeRewriteProposal({
426
424
  if (!proposal || !instruction || refining || resolveMutation.isPending)
427
425
  return;
428
426
  const repromptId = crypto.randomUUID();
429
- const pendingKey = designRepromptPendingStateKey(designId, fileId);
430
427
  const pending = {
431
428
  repromptId,
432
429
  designId,
@@ -440,7 +437,7 @@ export function NodeRewriteProposal({
440
437
  };
441
438
  setRefining(true);
442
439
  try {
443
- await setClientAppState(pendingKey, pending);
440
+ await callAction("begin-node-rewrite-request", pending);
444
441
  const submission = formatNodeRepromptSubmission({
445
442
  ...pending,
446
443
  priorProposalId: proposal.proposalId,
@@ -10,10 +10,7 @@ import {
10
10
  type ReviewThread,
11
11
  } from "@agent-native/core/client/review";
12
12
  import type { ReviewComment } from "@agent-native/core/review";
13
- import {
14
- designRepromptPendingStateKey,
15
- type NodeRewriteTarget,
16
- } from "@shared/node-rewrite";
13
+ import type { NodeRewriteTarget } from "@shared/node-rewrite";
17
14
  import {
18
15
  IconArrowUp,
19
16
  IconChevronDown,
@@ -874,10 +871,9 @@ export function ReviewCanvasPins({
874
871
  element = null;
875
872
  }
876
873
  }
877
- const stateKey = designRepromptPendingStateKey(resourceId, targetId);
878
874
  setAgentSubmitting(true);
879
875
  try {
880
- await setClientAppState(stateKey, pending);
876
+ await callAction("begin-node-rewrite-request", pending);
881
877
  const submission = formatNodeRepromptSubmission({
882
878
  ...pending,
883
879
  subtreeHtml:
@@ -15,6 +15,7 @@ export const MAX_NODE_REWRITE_PROPOSAL_BYTES = 256 * 1024;
15
15
  export type NodeRewriteTarget = EditIntentTarget;
16
16
 
17
17
  export interface PendingDesignReprompt {
18
+ status?: "pending";
18
19
  repromptId: string;
19
20
  designId: string;
20
21
  fileId: string;
@@ -26,6 +27,16 @@ export interface PendingDesignReprompt {
26
27
  priorRepromptId?: string;
27
28
  }
28
29
 
30
+ export interface NodeRewriteResolutionClaim extends Omit<
31
+ PendingDesignReprompt,
32
+ "status"
33
+ > {
34
+ status: "resolving";
35
+ claimId: string;
36
+ proposalId: string;
37
+ resolution: "accept";
38
+ }
39
+
29
40
  export interface NodeRewriteVariant {
30
41
  html: string;
31
42
  summary: string;
@@ -69,6 +80,7 @@ export function isPendingDesignReprompt(
69
80
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
70
81
  const pending = value as Partial<PendingDesignReprompt>;
71
82
  return (
83
+ (pending.status === undefined || pending.status === "pending") &&
72
84
  typeof pending.repromptId === "string" &&
73
85
  typeof pending.designId === "string" &&
74
86
  typeof pending.fileId === "string" &&
@@ -79,6 +91,26 @@ export function isPendingDesignReprompt(
79
91
  );
80
92
  }
81
93
 
94
+ export function isNodeRewriteResolutionClaim(
95
+ value: unknown,
96
+ ): value is NodeRewriteResolutionClaim {
97
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
98
+ const claim = value as Partial<NodeRewriteResolutionClaim>;
99
+ return (
100
+ claim.status === "resolving" &&
101
+ typeof claim.claimId === "string" &&
102
+ typeof claim.proposalId === "string" &&
103
+ claim.resolution === "accept" &&
104
+ typeof claim.repromptId === "string" &&
105
+ typeof claim.designId === "string" &&
106
+ typeof claim.fileId === "string" &&
107
+ typeof claim.baseVersionHash === "string" &&
108
+ typeof claim.instruction === "string" &&
109
+ typeof claim.createdAt === "string" &&
110
+ Boolean(claim.target)
111
+ );
112
+ }
113
+
82
114
  export interface NodeHtmlPreviewBridgeMessage {
83
115
  type: "node-html-preview";
84
116
  proposalId: string;
@@ -12,6 +12,7 @@ import { FeedbackButton } from "@agent-native/core/client/ui";
12
12
  import {
13
13
  IconArrowUp,
14
14
  IconPlus,
15
+ IconLoader2,
15
16
  IconMenu2,
16
17
  IconX,
17
18
  IconMessageCircle,
@@ -88,11 +89,9 @@ export function Sidebar() {
88
89
 
89
90
  function handleSkip() {
90
91
  setPopoverOpen(false);
91
- const tempId = crypto.randomUUID().replace(/-/g, "").slice(0, 10);
92
- navigate(`/forms/${tempId}`);
93
92
  createForm.mutate(
94
93
  { title: t("sidebar.untitledForm") },
95
- { onSuccess: (form) => navigate(`/forms/${form.id}`, { replace: true }) },
94
+ { onSuccess: (form) => navigate(`/forms/${form.id}`) },
96
95
  );
97
96
  }
98
97
 
@@ -171,7 +170,11 @@ export function Sidebar() {
171
170
  size="sm"
172
171
  className="min-h-10 px-2 text-xs text-muted-foreground active:scale-[0.96] transition-[background-color,color,transform]"
173
172
  onClick={handleSkip}
173
+ disabled={createForm.isPending}
174
174
  >
175
+ {createForm.isPending && (
176
+ <IconLoader2 className="h-3 w-3 animate-spin" />
177
+ )}
175
178
  {t("sidebar.skipPrompt")}
176
179
  </Button>
177
180
  <span className="text-[11px] text-muted-foreground/70">
@@ -626,20 +626,15 @@ export function FormBuilderPage() {
626
626
  </Tooltip>
627
627
  )}
628
628
 
629
- <Tooltip>
630
- <TooltipTrigger asChild>
631
- <span className="inline-flex">
629
+ {form.status === "published" && (
630
+ <Tooltip>
631
+ <TooltipTrigger asChild>
632
632
  <Button
633
633
  variant="ghost"
634
634
  size="icon"
635
635
  className="h-10 w-10 active:scale-[0.96] motion-reduce:active:scale-100"
636
636
  onClick={copyShareLink}
637
- disabled={form.status !== "published"}
638
- aria-label={
639
- form.status === "published"
640
- ? t("builder.copyPublicFormLink")
641
- : t("builder.publishBeforeCopyPublicFormLink")
642
- }
637
+ aria-label={t("builder.copyPublicFormLink")}
643
638
  >
644
639
  <span className="relative inline-flex h-4 w-4 items-center justify-center">
645
640
  <IconCopy
@@ -660,16 +655,14 @@ export function FormBuilderPage() {
660
655
  />
661
656
  </span>
662
657
  </Button>
663
- </span>
664
- </TooltipTrigger>
665
- <TooltipContent>
666
- {form.status === "published"
667
- ? copied
658
+ </TooltipTrigger>
659
+ <TooltipContent>
660
+ {copied
668
661
  ? t("builder.publicLinkCopied")
669
- : t("builder.copyPublishedPublicLink")
670
- : t("builder.publishBeforeCopyPublicLink")}
671
- </TooltipContent>
672
- </Tooltip>
662
+ : t("builder.copyPublishedPublicLink")}
663
+ </TooltipContent>
664
+ </Tooltip>
665
+ )}
673
666
 
674
667
  <Tooltip>
675
668
  <TooltipTrigger asChild>
@@ -7,6 +7,7 @@ import {
7
7
  import { VisibilityBadge } from "@agent-native/toolkit/sharing";
8
8
  import {
9
9
  IconPlus,
10
+ IconLoader2,
10
11
  IconDots,
11
12
  IconTrash,
12
13
  IconCopy,
@@ -100,11 +101,9 @@ export function FormsListPage() {
100
101
  }, [forms]);
101
102
 
102
103
  function handleCreate() {
103
- const tempId = crypto.randomUUID().replace(/-/g, "").slice(0, 10);
104
- navigate(`/forms/${tempId}`);
105
104
  createForm.mutate(
106
105
  { title: t("forms.untitled") },
107
- { onSuccess: (form) => navigate(`/forms/${form.id}`, { replace: true }) },
106
+ { onSuccess: (form) => navigate(`/forms/${form.id}`) },
108
107
  );
109
108
  }
110
109
 
@@ -114,16 +113,21 @@ export function FormsListPage() {
114
113
  () => (
115
114
  <Button
116
115
  onClick={handleCreate}
116
+ disabled={createForm.isPending}
117
117
  size="sm"
118
118
  className="min-h-10 shrink-0 cursor-pointer active:scale-[0.96] transition-[background-color,box-shadow,transform]"
119
119
  >
120
- <IconPlus className="h-3.5 w-3.5" />
120
+ {createForm.isPending ? (
121
+ <IconLoader2 className="h-3.5 w-3.5 animate-spin" />
122
+ ) : (
123
+ <IconPlus className="h-3.5 w-3.5" />
124
+ )}
121
125
  <span className="hidden sm:inline">{t("forms.newForm")}</span>
122
126
  <span className="sm:hidden">{t("forms.new")}</span>
123
127
  </Button>
124
128
  ),
125
129
  // eslint-disable-next-line react-hooks/exhaustive-deps
126
- [],
130
+ [createForm.isPending],
127
131
  );
128
132
  useSetHeaderActions(headerActions);
129
133
 
@@ -9,6 +9,7 @@ import {
9
9
  } from "@agent-native/core/client/collab";
10
10
  import { callAction } from "@agent-native/core/client/hooks";
11
11
  import { isEmbedAuthActive } from "@agent-native/core/client/host";
12
+ import { useOrg } from "@agent-native/core/client/org";
12
13
  import { nanoid } from "nanoid";
13
14
  import {
14
15
  createContext,
@@ -950,6 +951,7 @@ export const defaultSlideContent: Record<SlideLayout, string> = {
950
951
  };
951
952
 
952
953
  export function DeckProvider({ children }: { children: ReactNode }) {
954
+ const { data: org } = useOrg();
953
955
  const [decks, setDecks] = useState<Deck[]>([]);
954
956
  const [loading, setLoading] = useState(true);
955
957
  const [loadError, setLoadError] = useState(false);
@@ -1269,6 +1271,22 @@ export function DeckProvider({ children }: { children: ReactNode }) {
1269
1271
  });
1270
1272
  }, [resetDeckBaseline]);
1271
1273
 
1274
+ // Switching orgs re-scopes list-decks server-side but leaves this context's
1275
+ // in-memory list untouched, so the previous org's decks linger. Reload when
1276
+ // the org id actually changes; skip the first observed id so we don't double
1277
+ // up on the mount fetch above.
1278
+ const lastOrgIdRef = useRef<string | null | undefined>(undefined);
1279
+ useEffect(() => {
1280
+ const orgId = org?.orgId ?? null;
1281
+ if (lastOrgIdRef.current === undefined) {
1282
+ lastOrgIdRef.current = orgId;
1283
+ return;
1284
+ }
1285
+ if (lastOrgIdRef.current === orgId) return;
1286
+ lastOrgIdRef.current = orgId;
1287
+ void reloadDecks();
1288
+ }, [org?.orgId, reloadDecks]);
1289
+
1272
1290
  // Fallback polling for deck list + open-deck changes. SSE is the primary
1273
1291
  // path; this catches agent/db writes that bypass it without hammering idle
1274
1292
  // editor pages.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-21
4
+ ---
5
+
6
+ Decks list now refreshes when you switch organizations