@agent-native/core 0.149.6 → 0.151.0

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 (177) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  3. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +83 -45
  4. package/corpus/templates/analytics/changelog/2026-08-11-analytics-dashboards-keep-the-selected-tab-when-reopened.md +6 -0
  5. package/corpus/templates/assets/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  6. package/corpus/templates/brain/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  7. package/corpus/templates/calendar/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  8. package/corpus/templates/chat/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  9. package/corpus/templates/clips/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  10. package/corpus/templates/clips/changelog/2026-08-11-clips-recovers-from-transient-updates-and-avoids-macos-priva.md +6 -0
  11. package/corpus/templates/clips/desktop/src/app.tsx +32 -176
  12. package/corpus/templates/clips/desktop/src/lib/updater.ts +52 -37
  13. package/corpus/templates/clips/desktop/src/styles.css +1 -166
  14. package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +2 -0
  15. package/corpus/templates/clips/desktop/src-tauri/Info.plist +4 -0
  16. package/corpus/templates/clips/desktop/src-tauri/src/eventkit.rs +28 -1
  17. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +2 -0
  18. package/corpus/templates/clips/desktop/src-tauri/src/native_speech.rs +48 -1
  19. package/corpus/templates/clips/desktop/src-tauri/src/permission_status.rs +3 -0
  20. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +5 -0
  21. package/corpus/templates/clips/server/plugins/auth.ts +3 -0
  22. package/corpus/templates/content/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  23. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +2 -1
  24. package/corpus/templates/design/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  25. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +5 -1
  26. package/corpus/templates/design/changelog/2026-08-11-design-agent-prompts-copy-successfully-in-the-desktop-app.md +6 -0
  27. package/corpus/templates/dispatch/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  28. package/corpus/templates/dispatch/app/global.css +2 -2
  29. package/corpus/templates/dispatch/changelog/2026-08-10-desktop-workspace-sign-in.md +6 -0
  30. package/corpus/templates/dispatch/changelog/2026-08-11-dispatch-no-longer-shows-the-generic-chat-starter-among-defa.md +6 -0
  31. package/corpus/templates/dispatch/changelog/2026-08-11-integrations-and-scheduled-work-open-as-first-class-electron-pages.md +6 -0
  32. package/corpus/templates/dispatch/changelog/2026-08-11-sidebar-text-is-slightly-easier-to-read-in-light-and-dark-mo.md +6 -0
  33. package/corpus/templates/dispatch/server/lib/identity-sso.ts +57 -31
  34. package/corpus/templates/dispatch/server/plugins/feature-flags.ts +5 -0
  35. package/corpus/templates/dispatch/server/plugins/identity-sso.ts +82 -33
  36. package/corpus/templates/dispatch/server/plugins/org-apps-directory.ts +7 -15
  37. package/corpus/templates/dispatch/server/plugins/setup-dispatch.ts +9 -1
  38. package/corpus/templates/dispatch/shared/feature-flags.ts +15 -0
  39. package/corpus/templates/factory/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  40. package/corpus/templates/forms/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  41. package/corpus/templates/forms/.agents/skills/form-responses/SKILL.md +8 -7
  42. package/corpus/templates/forms/app/hooks/use-forms.ts +8 -1
  43. package/corpus/templates/forms/changelog/2026-08-11-fixed-public-forms-so-conditional-questions-stay-hidden-unti.md +6 -0
  44. package/corpus/templates/forms/server/handlers/submissions.ts +2 -1
  45. package/corpus/templates/forms/server/lib/public-form-ssr.ts +37 -2
  46. package/corpus/templates/forms/shared/page-url.ts +54 -0
  47. package/corpus/templates/forms/shared/types.ts +4 -4
  48. package/corpus/templates/mail/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  49. package/corpus/templates/plan/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  50. package/corpus/templates/slides/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  51. package/corpus/templates/slides/actions/add-slide.ts +0 -27
  52. package/corpus/templates/slides/actions/import-file.ts +28 -9
  53. package/corpus/templates/slides/actions/view-screen.ts +4 -4
  54. package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +8 -1
  55. package/corpus/templates/slides/app/components/editor/AiEditingMarker.tsx +25 -0
  56. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +43 -48
  57. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +9 -6
  58. package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +1 -9
  59. package/corpus/templates/slides/app/components/editor/ImageDropPromptPopover.tsx +2 -0
  60. package/corpus/templates/slides/app/components/editor/ImageOverlay.tsx +0 -26
  61. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +904 -217
  62. package/corpus/templates/slides/app/components/editor/canvas-interactions/index.ts +1 -0
  63. package/corpus/templates/slides/app/components/editor/canvas-interactions/slides-canvas-adapter.ts +11 -0
  64. package/corpus/templates/slides/app/components/editor/inline-edit-session.ts +16 -0
  65. package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +22 -6
  66. package/corpus/templates/slides/app/components/editor/style-clipboard.ts +61 -0
  67. package/corpus/templates/slides/app/components/layout/Layout.tsx +26 -2
  68. package/corpus/templates/slides/app/components/ui/context-menu.tsx +1 -0
  69. package/corpus/templates/slides/app/context/DeckContext.tsx +75 -25
  70. package/corpus/templates/slides/app/global.css +1 -1
  71. package/corpus/templates/slides/app/i18n/en-US.ts +6 -1
  72. package/corpus/templates/slides/app/lib/create-deck-generation.ts +1 -1
  73. package/corpus/templates/slides/app/lib/image-drop-to-agent.ts +12 -12
  74. package/corpus/templates/slides/app/lib/slide-agent-context.ts +52 -0
  75. package/corpus/templates/slides/app/lib/slide-image-replacement.ts +61 -0
  76. package/corpus/templates/slides/app/pages/DeckEditor.tsx +55 -116
  77. package/corpus/templates/slides/app/pages/Index.tsx +28 -3
  78. package/corpus/templates/slides/changelog/2026-08-11-agent-chat-now-follows-the-current-slide-and-selection.md +6 -0
  79. package/corpus/templates/slides/changelog/2026-08-11-deck-titles-now-use-slide-content-instead-of-placeholder-filenames.md +6 -0
  80. package/corpus/templates/slides/changelog/2026-08-11-history-now-opens-from-the-deck-overflow-menu.md +6 -0
  81. package/corpus/templates/slides/changelog/2026-08-11-images-can-be-dropped-directly-onto-slides-and-resized-or-re.md +6 -0
  82. package/corpus/templates/slides/changelog/2026-08-11-new-decks-now-transition-directly-to-a-full-page-editor-load.md +6 -0
  83. package/corpus/templates/slides/changelog/2026-08-11-selected-slide-layers-can-be-nudged-precisely-with-arrow-key.md +6 -0
  84. package/corpus/templates/slides/changelog/2026-08-11-slides-can-copy-and-paste-element-styles-with-cmd-option-c-v.md +6 -0
  85. package/corpus/templates/slides/changelog/2026-08-11-slides-shows-one-compact-ai-marker-and-keeps-your-current-sl.md +6 -0
  86. package/corpus/templates/slides/changelog/2026-08-11-text-edits-preserve-slide-layout-when-no-changes-are-made.md +6 -0
  87. package/corpus/templates/slides/changelog/2026-08-11-the-style-toolbar-now-uses-the-full-available-width.md +6 -0
  88. package/corpus/templates/slides/changelog/2026-08-11-undo-and-redo-now-move-one-visible-slide-state-at-a-time.md +6 -0
  89. package/corpus/templates/slides/server/lib/chat-attachments.ts +6 -2
  90. package/corpus/templates/slides/shared/deck-title.ts +56 -0
  91. package/corpus/templates/tasks/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  92. package/dist/agent/run-store.js +6 -1
  93. package/dist/agent/thread-data-builder.js +19 -44
  94. package/dist/agent/types.d.ts +13 -0
  95. package/dist/catalog.json +1 -0
  96. package/dist/cli/create.js +1 -1
  97. package/dist/client/AssistantChat.js +58 -10
  98. package/dist/client/FileStorageSetupCard.d.ts +6 -0
  99. package/dist/client/FileStorageSetupCard.js +18 -0
  100. package/dist/client/NewWorkspaceAppFlow.js +10 -0
  101. package/dist/client/agent-chat-adapter.js +87 -11
  102. package/dist/client/agent-chat.d.ts +1 -1
  103. package/dist/client/agent-chat.js +8 -3
  104. package/dist/client/chat/attachment-adapters.js +10 -0
  105. package/dist/client/chat/message-components.d.ts +4 -1
  106. package/dist/client/chat/message-components.js +37 -13
  107. package/dist/client/chat/tool-call-display.js +14 -0
  108. package/dist/client/chat-first/apps-rail.d.ts +1 -1
  109. package/dist/client/chat-first/apps-rail.js +6 -6
  110. package/dist/client/chat-first/copy.js +1 -0
  111. package/dist/client/chat-first/types.d.ts +1 -0
  112. package/dist/client/extensions/EmbeddedExtension.js +1 -1
  113. package/dist/client/extensions/ExtensionViewer.js +1 -1
  114. package/dist/client/extensions/InlineExtensionFrame.js +1 -1
  115. package/dist/client/onboarding/FirstRunOnboarding.js +17 -2
  116. package/dist/client/settings/FileStorageSettingsForm.d.ts +2 -0
  117. package/dist/client/settings/FileStorageSettingsForm.js +113 -0
  118. package/dist/client/settings/SettingsPanel.js +2 -1
  119. package/dist/client/sse-event-processor.js +21 -0
  120. package/dist/collab/awareness.d.ts +2 -2
  121. package/dist/collab/struct-routes.d.ts +1 -1
  122. package/dist/extensions/actions.js +2 -2
  123. package/dist/extensions/html-shell.js +3 -1
  124. package/dist/file-upload/pre-upload-attachments.d.ts +15 -10
  125. package/dist/file-upload/pre-upload-attachments.js +68 -24
  126. package/dist/file-upload/registry.d.ts +4 -5
  127. package/dist/file-upload/registry.js +7 -14
  128. package/dist/file-upload/s3.d.ts +9 -0
  129. package/dist/file-upload/s3.js +168 -0
  130. package/dist/file-upload/types.d.ts +3 -3
  131. package/dist/file-upload/types.js +2 -2
  132. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  133. package/dist/notifications/routes.d.ts +6 -6
  134. package/dist/observability/routes.d.ts +3 -3
  135. package/dist/onboarding/app-profile.js +23 -1
  136. package/dist/onboarding/default-steps.js +69 -0
  137. package/dist/onboarding/types.d.ts +2 -0
  138. package/dist/progress/routes.d.ts +1 -1
  139. package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
  140. package/dist/provider-api/actions/provider-api.d.ts +8 -8
  141. package/dist/secrets/routes.d.ts +3 -3
  142. package/dist/server/agent-chat/browser-team-tools.js +20 -0
  143. package/dist/server/agent-chat-plugin.js +12 -3
  144. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  145. package/dist/server/auth.js +45 -21
  146. package/dist/server/better-auth-instance.d.ts +2 -0
  147. package/dist/server/better-auth-instance.js +2 -2
  148. package/dist/server/core-routes-plugin.d.ts +1 -0
  149. package/dist/server/core-routes-plugin.js +26 -16
  150. package/dist/server/identity-sso-store.d.ts +3 -0
  151. package/dist/server/identity-sso-store.js +45 -0
  152. package/dist/server/identity-sso.d.ts +20 -10
  153. package/dist/server/identity-sso.js +79 -31
  154. package/dist/server/index.d.ts +1 -1
  155. package/dist/server/index.js +1 -1
  156. package/dist/server/realtime-token.d.ts +1 -1
  157. package/dist/server/transcribe-voice.d.ts +1 -1
  158. package/dist/templates/chat/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  159. package/dist/templates/default/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  160. package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +24 -1
  161. package/dist/templates/workspace-core/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  162. package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -2
  163. package/dist/templates/workspace-core/.agents/skills/extensions/references/api.md +1 -1
  164. package/dist/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +3 -1
  165. package/docs/content/authentication.mdx +27 -0
  166. package/docs/content/cross-app-sso.mdx +38 -8
  167. package/docs/content/extensions.mdx +15 -15
  168. package/docs/content/file-uploads.mdx +10 -12
  169. package/docs/content/generative-ui.mdx +6 -1
  170. package/package.json +2 -2
  171. package/src/templates/chat/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  172. package/src/templates/default/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  173. package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +24 -1
  174. package/src/templates/workspace-core/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  175. package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -2
  176. package/src/templates/workspace-core/.agents/skills/extensions/references/api.md +1 -1
  177. package/src/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +3 -1
@@ -0,0 +1,54 @@
1
+ export const SENSITIVE_QUERY_PARAMS = [
2
+ "password",
3
+ "p",
4
+ "token",
5
+ "state",
6
+ "code",
7
+ "share",
8
+ "share_token",
9
+ "bridge",
10
+ "access_token",
11
+ "id_token",
12
+ "refresh_token",
13
+ "authorization",
14
+ "api_key",
15
+ "key",
16
+ "secret",
17
+ "session",
18
+ ] as const;
19
+
20
+ const SENSITIVE_QUERY_PARAM_SET = new Set<string>(SENSITIVE_QUERY_PARAMS);
21
+
22
+ /**
23
+ * Keep response page context useful while never retaining URL-bar secrets.
24
+ * This mirrors the framework feedback URL scrubber for Forms-owned public
25
+ * submissions and re-checks metadata supplied by embeds on the server.
26
+ */
27
+ export function scrubPageUrl(value: unknown): string | null {
28
+ if (typeof value !== "string") return null;
29
+ const trimmed = value.trim();
30
+ if (!trimmed) return null;
31
+ if (!URL.canParse(trimmed)) return null;
32
+
33
+ const url = new URL(trimmed);
34
+ if (url.protocol !== "http:" && url.protocol !== "https:") return null;
35
+ url.username = "";
36
+ url.password = "";
37
+ scrubParams(url.searchParams);
38
+
39
+ if (url.hash.includes("=")) {
40
+ const hashParams = new URLSearchParams(url.hash.slice(1));
41
+ scrubParams(hashParams);
42
+ url.hash = hashParams.toString();
43
+ }
44
+
45
+ return url.toString();
46
+ }
47
+
48
+ function scrubParams(params: URLSearchParams) {
49
+ for (const key of params.keys()) {
50
+ if (SENSITIVE_QUERY_PARAM_SET.has(key.toLowerCase())) {
51
+ params.set(key, "<redacted>");
52
+ }
53
+ }
54
+ }
@@ -155,15 +155,15 @@ export interface FormResponse {
155
155
  /** Real submitter email when known; synthetic anonymous-owner ids are hidden. */
156
156
  submitterEmail?: string | null;
157
157
  /**
158
- * URL of the page the respondent was on, forwarded by trusted embeds (e.g.
159
- * the framework FeedbackButton) as a hidden pass-through field. Null when the
160
- * submission carried no page context (e.g. a direct fill on the public page).
158
+ * URL of the direct public form or trusted embed source page. Sensitive URL
159
+ * query keys are scrubbed before it is stored; null when no page context is
160
+ * available or anonymous mode suppresses response metadata.
161
161
  */
162
162
  pageUrl?: string | null;
163
163
  /**
164
164
  * Runtime shell the feedback was sent from — "web", "electron", or "tauri" —
165
165
  * forwarded by trusted embeds as a hidden pass-through field. Null when
166
- * unknown (e.g. a direct fill on the public page).
166
+ * unknown or anonymous mode suppresses response metadata.
167
167
  */
168
168
  clientSurface?: string | null;
169
169
  }
@@ -49,6 +49,34 @@ sendToAgentChat({
49
49
  });
50
50
  ```
51
51
 
52
+ ### Keep user text separate from injected context
53
+
54
+ Treat the visible `message` as the user's request or the shortest clear
55
+ description of an app-initiated operation. Put context the user did not type —
56
+ IDs, URLs, filenames, current selection or screen state, serialized records,
57
+ upload instructions, and bounded source excerpts — in the `context` field.
58
+ `context` is model input carried through the agent turn; it is stripped from
59
+ the rendered user message, so do not concatenate it into `message` with labels,
60
+ blank-line sections, or `<context>` tags yourself.
61
+
62
+ Use the related surface for each kind of supporting input:
63
+
64
+ | Surface | Use for |
65
+ | --- | --- |
66
+ | `message` | User-authored intent or a concise app action description |
67
+ | `context` | Derived metadata and bounded text needed to carry out that intent |
68
+ | `setAgentChatContextItem` | Context staged for a later user-submitted prompt; keep it keyed so updates replace stale context |
69
+ | `images`, `referenceImagePaths`, attachments | Binary or visual inputs; describe only the handling instructions in `context` |
70
+
71
+ If an app builds a prompt from a form, selection, upload, or editor state, keep
72
+ the visible message short and pass the assembled details as `context`. Preserve
73
+ the user's actual freeform text in `message` when it is the request; do not
74
+ restate it as metadata in `context` unless the agent needs a structured copy.
75
+
76
+ This boundary applies to auto-submitted turns and prefills. A hidden context
77
+ field is not a license to send unbounded records or secrets: cap excerpts,
78
+ prefer stable IDs and URLs, and use an action or resource lookup for full data.
79
+
52
80
  **From the UI, in the background:**
53
81
 
54
82
  ```ts
@@ -49,6 +49,34 @@ sendToAgentChat({
49
49
  });
50
50
  ```
51
51
 
52
+ ### Keep user text separate from injected context
53
+
54
+ Treat the visible `message` as the user's request or the shortest clear
55
+ description of an app-initiated operation. Put context the user did not type —
56
+ IDs, URLs, filenames, current selection or screen state, serialized records,
57
+ upload instructions, and bounded source excerpts — in the `context` field.
58
+ `context` is model input carried through the agent turn; it is stripped from
59
+ the rendered user message, so do not concatenate it into `message` with labels,
60
+ blank-line sections, or `<context>` tags yourself.
61
+
62
+ Use the related surface for each kind of supporting input:
63
+
64
+ | Surface | Use for |
65
+ | --- | --- |
66
+ | `message` | User-authored intent or a concise app action description |
67
+ | `context` | Derived metadata and bounded text needed to carry out that intent |
68
+ | `setAgentChatContextItem` | Context staged for a later user-submitted prompt; keep it keyed so updates replace stale context |
69
+ | `images`, `referenceImagePaths`, attachments | Binary or visual inputs; describe only the handling instructions in `context` |
70
+
71
+ If an app builds a prompt from a form, selection, upload, or editor state, keep
72
+ the visible message short and pass the assembled details as `context`. Preserve
73
+ the user's actual freeform text in `message` when it is the request; do not
74
+ restate it as metadata in `context` unless the agent needs a structured copy.
75
+
76
+ This boundary applies to auto-submitted turns and prefills. A hidden context
77
+ field is not a license to send unbounded records or secrets: cap excerpts,
78
+ prefer stable IDs and URLs, and use an action or resource lookup for full data.
79
+
52
80
  **From the UI, in the background:**
53
81
 
54
82
  ```ts
@@ -49,6 +49,34 @@ sendToAgentChat({
49
49
  });
50
50
  ```
51
51
 
52
+ ### Keep user text separate from injected context
53
+
54
+ Treat the visible `message` as the user's request or the shortest clear
55
+ description of an app-initiated operation. Put context the user did not type —
56
+ IDs, URLs, filenames, current selection or screen state, serialized records,
57
+ upload instructions, and bounded source excerpts — in the `context` field.
58
+ `context` is model input carried through the agent turn; it is stripped from
59
+ the rendered user message, so do not concatenate it into `message` with labels,
60
+ blank-line sections, or `<context>` tags yourself.
61
+
62
+ Use the related surface for each kind of supporting input:
63
+
64
+ | Surface | Use for |
65
+ | --- | --- |
66
+ | `message` | User-authored intent or a concise app action description |
67
+ | `context` | Derived metadata and bounded text needed to carry out that intent |
68
+ | `setAgentChatContextItem` | Context staged for a later user-submitted prompt; keep it keyed so updates replace stale context |
69
+ | `images`, `referenceImagePaths`, attachments | Binary or visual inputs; describe only the handling instructions in `context` |
70
+
71
+ If an app builds a prompt from a form, selection, upload, or editor state, keep
72
+ the visible message short and pass the assembled details as `context`. Preserve
73
+ the user's actual freeform text in `message` when it is the request; do not
74
+ restate it as metadata in `context` unless the agent needs a structured copy.
75
+
76
+ This boundary applies to auto-submitted turns and prefills. A hidden context
77
+ field is not a license to send unbounded records or secrets: cap excerpts,
78
+ prefer stable IDs and URLs, and use an action or resource lookup for full data.
79
+
52
80
  **From the UI, in the background:**
53
81
 
54
82
  ```ts
@@ -24,10 +24,6 @@ import {
24
24
  awaitLayoutFitCheck,
25
25
  formatOverflowForTool,
26
26
  } from "./_await-fit-check.js";
27
- import {
28
- readAppStateForCurrentTab,
29
- writeAppStateForCurrentTab,
30
- } from "./_tab-state.js";
31
27
  // Use the shared, globalThis-pinned per-deck lock so add-slide, update-slide,
32
28
  // and the browser's patch-deck all serialise against the SAME lock — writes to
33
29
  // different slides of the same deck can never clobber each other.
@@ -383,29 +379,6 @@ export default defineAction({
383
379
  // Include the new slideId + agent actor (backwards-compatible payload).
384
380
  notifyClients(deckId, { slideId: newSlideId, actor: "agent" });
385
381
 
386
- // Nudge any open editor onto the new slide so the renderer measures
387
- // IT (not whichever slide was previously selected). Only fires when an
388
- // editor is open on this deck; navigation state is a no-op if nobody
389
- // is watching.
390
- const nav = (await readAppStateForCurrentTab("navigation", {
391
- fallbackToGlobal: false,
392
- }).catch(() => null)) as {
393
- view?: string;
394
- deckId?: string;
395
- } | null;
396
- if (nav?.view === "editor" && nav.deckId === deckId) {
397
- await writeAppStateForCurrentTab("navigate", {
398
- deckId,
399
- slideIndex: insertIndex,
400
- // Unique-per-write token. The UI's `use-navigation-state` hook
401
- // dedups by this so a race between the GET and the consume-DELETE
402
- // doesn't cause the same command to be re-applied repeatedly
403
- // (which previously bounced the editor between slides whenever the
404
- // agent path errored partway through a turn).
405
- _writeId: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
406
- }).catch(() => {});
407
- }
408
-
409
382
  // Wait briefly for the editor to render the new slide and report its
410
383
  // measured fit. If we get an "overflows" signal, append the auto-fix
411
384
  // hint so the agent can make one bounded structural repair. Timeout = no
@@ -28,7 +28,10 @@ import {
28
28
  DEFAULT_ASPECT_RATIO,
29
29
  type AspectRatio,
30
30
  } from "../shared/aspect-ratios.js";
31
- import { isOpaqueDeckTitle } from "../shared/deck-title.js";
31
+ import {
32
+ isOpaqueDeckTitle,
33
+ resolveImportedDeckTitle,
34
+ } from "../shared/deck-title.js";
32
35
  import { readUserUploadedFile } from "./_uploaded-files.js";
33
36
  import { withDeckLock } from "./patch-deck.js";
34
37
 
@@ -182,17 +185,18 @@ export default defineAction({
182
185
  presentation.slides[0].heightEmu,
183
186
  )
184
187
  : undefined;
185
- await appendDeckSlides(
188
+ const importedTitle = await appendDeckSlides(
186
189
  deckId,
187
190
  title,
188
191
  slides,
189
192
  "import-file:pptx",
190
193
  aspectRatio,
191
194
  sourceImport,
195
+ pptxResults[0]?.sourceText,
192
196
  );
193
197
  return {
194
198
  format: "pptx",
195
- title,
199
+ title: importedTitle,
196
200
  slideCount: slides.length,
197
201
  theme: presentation.theme,
198
202
  deckId,
@@ -241,10 +245,18 @@ export default defineAction({
241
245
  layout: "content",
242
246
  notes: "",
243
247
  }));
244
- await appendDeckSlides(deckId, title, slides, "import-file:docx");
248
+ const importedTitle = await appendDeckSlides(
249
+ deckId,
250
+ title,
251
+ slides,
252
+ "import-file:docx",
253
+ undefined,
254
+ undefined,
255
+ doc.text,
256
+ );
245
257
  return {
246
258
  format: "docx",
247
- title,
259
+ title: importedTitle,
248
260
  sectionCount: doc.sections.length,
249
261
  slideCount: slides.length,
250
262
  textLength: doc.text.length,
@@ -520,18 +532,19 @@ async function importPdfPagesWithFidelity(args: {
520
532
  slides: imported.map((entry) => entry.snapshot),
521
533
  });
522
534
 
523
- await appendDeckSlides(
535
+ const importedTitle = await appendDeckSlides(
524
536
  deckId,
525
537
  title,
526
538
  slides,
527
539
  "import-file:pdf",
528
540
  aspectRatio,
529
541
  sourceImport,
542
+ imported[0]?.snapshot.text,
530
543
  );
531
544
 
532
545
  return {
533
546
  format: "pdf",
534
- title,
547
+ title: importedTitle,
535
548
  pageCount: slides.length,
536
549
  slideCount: slides.length,
537
550
  aspectRatio,
@@ -674,7 +687,8 @@ async function appendDeckSlides(
674
687
  source: string,
675
688
  aspectRatio?: AspectRatio,
676
689
  sourceImport?: ReturnType<typeof buildSourceImportMetadata>,
677
- ) {
690
+ titleSource?: unknown,
691
+ ): Promise<string> {
678
692
  await assertAccess("deck", deckId, "editor");
679
693
 
680
694
  // Read-modify-write under the shared per-deck lock used by patch-deck /
@@ -682,6 +696,7 @@ async function appendDeckSlides(
682
696
  // with another import or an editor/agent slide mutation on the same deck
683
697
  // could read stale data and clobber the other write when both save the
684
698
  // whole decks.data blob back.
699
+ let resolvedTitle = title;
685
700
  const now = await withDeckLock(deckId, async () => {
686
701
  const db = getDb();
687
702
  const existing = await db
@@ -706,7 +721,10 @@ async function appendDeckSlides(
706
721
  // had no slides yet, otherwise resizing the canvas mid-deck would distort
707
722
  // every slide already on it.
708
723
  const hadExistingSlides = previousSlides.length > 0;
709
- const nextTitle = hadExistingSlides ? (existing[0].title ?? title) : title;
724
+ const nextTitle = hadExistingSlides
725
+ ? (existing[0].title ?? title)
726
+ : resolveImportedDeckTitle(title, titleSource ?? slides[0]?.content);
727
+ resolvedTitle = nextTitle;
710
728
  const nextSourceImport = sourceImport
711
729
  ? mergeSourceImportMetadata(
712
730
  sourceImportForDeck(previousData.sourceImport),
@@ -736,6 +754,7 @@ async function appendDeckSlides(
736
754
 
737
755
  notifyClients(deckId);
738
756
  await writeAppState("refresh-signal", { ts: now, source });
757
+ return resolvedTitle;
739
758
  }
740
759
 
741
760
  function safeParseDeckData(raw: string): Record<string, unknown> {
@@ -162,12 +162,12 @@ export default defineAction({
162
162
  style?: Record<string, unknown>;
163
163
  }>;
164
164
  } | null;
165
- if (
166
- selection &&
167
- (!selection.slideId || selection.slideId === currentSlide?.id)
168
- ) {
165
+ if (selection && currentSlide && selection.slideId === currentSlide.id) {
169
166
  lines.push(``);
170
167
  lines.push(`### Current visual selection`);
168
+ lines.push(
169
+ `selectionSlideId: ${selection.slideId} (matches currentSlideId)`,
170
+ );
171
171
  lines.push(`mode: ${selection.mode ?? "unknown"}`);
172
172
  lines.push(`activeTool: ${selection.activeTool ?? "select"}`);
173
173
  if (Array.isArray(selection.items) && selection.items.length > 0) {
@@ -581,8 +581,15 @@ export function DesignSystemSetup({
581
581
  }. Present a summary for review.`,
582
582
  );
583
583
 
584
+ const message =
585
+ parts[0] ?? "Set up a design system from the selected sources.";
586
+ const contextParts = parts.slice(1);
584
587
  openAgentSidebar();
585
- sendToAgentChat({ message: parts.join("\n"), submit: true });
588
+ sendToAgentChat({
589
+ message,
590
+ context: contextParts.join("\n"),
591
+ submit: true,
592
+ });
586
593
  toast(t("designSystemSetup.generationStarted"), {
587
594
  description: t("designSystemSetup.generationStartedDescription"),
588
595
  });
@@ -0,0 +1,25 @@
1
+ import { useT } from "@agent-native/core/client/i18n";
2
+ import { DEFAULT_AGENT_IDENTITY } from "@agent-native/toolkit/collab-ui";
3
+
4
+ import { cn } from "@/lib/utils";
5
+
6
+ /** The single compact marker used for every AI-editing state in Slides. */
7
+ export function AiEditingMarker({ className }: { className?: string }) {
8
+ const t = useT();
9
+ const label = t("raw.aiEditing");
10
+
11
+ return (
12
+ <span
13
+ aria-label={label}
14
+ className={cn(
15
+ "inline-flex size-5 shrink-0 items-center justify-center rounded-full text-[9px] font-semibold leading-none text-primary-foreground",
16
+ className,
17
+ )}
18
+ role="img"
19
+ style={{ backgroundColor: DEFAULT_AGENT_IDENTITY.color }}
20
+ title={label}
21
+ >
22
+ AI
23
+ </span>
24
+ );
25
+ }
@@ -2,13 +2,13 @@ import {
2
2
  agentNativePath,
3
3
  appBasePath,
4
4
  } from "@agent-native/core/client/api-path";
5
- import {
6
- type AttributedRecentEdit,
7
- type CollabUser,
5
+ import type {
6
+ AttributedRecentEdit,
7
+ CollabUser,
8
8
  } from "@agent-native/core/client/collab";
9
9
  import { useAvatarUrl } from "@agent-native/core/client/hooks";
10
10
  import { useT } from "@agent-native/core/client/i18n";
11
- import { RecentEditHighlights } from "@agent-native/toolkit/collab-ui";
11
+ import { DEFAULT_AGENT_IDENTITY } from "@agent-native/toolkit/collab-ui";
12
12
  import {
13
13
  useSortable,
14
14
  SortableContext,
@@ -22,6 +22,7 @@ import { useCallback } from "react";
22
22
 
23
23
  import SlideRenderer from "@/components/deck/SlideRenderer";
24
24
  import type { SlideOverflowInfo } from "@/components/deck/SlideRenderer";
25
+ import { AiEditingMarker } from "@/components/editor/AiEditingMarker";
25
26
  import GeneratingSlidePreview from "@/components/editor/GeneratingSlidePreview";
26
27
  import {
27
28
  Tooltip,
@@ -43,14 +44,14 @@ interface EditorSidebarProps {
43
44
  readOnly?: boolean;
44
45
  /** Presence map: slideId → list of users currently viewing that slide */
45
46
  slidePresence?: Map<string, CollabUser[]>;
46
- /** Lingering recent edits (e.g. agent edits) to highlight over thumbnails. */
47
+ /** Lingering recent edits used to show the compact AI marker. */
47
48
  recentEdits?: AttributedRecentEdit[];
48
49
  /** Deck aspect ratio (defaults to 16:9 when omitted) */
49
50
  aspectRatio?: AspectRatio;
50
51
  /** Active deck design system used by slide content tokens. */
51
52
  designSystem?: DesignSystemData;
52
53
  /** The next slide while the agent is preparing its HTML. */
53
- generatingSlide?: { index: number; content?: string | null };
54
+ generatingSlide?: { index: number };
54
55
  generatingSlideSelected?: boolean;
55
56
  onSelectGeneratingSlide?: () => void;
56
57
  }
@@ -72,6 +73,13 @@ function slideIdFromEdit(edit: AttributedRecentEdit): string | null {
72
73
  return null;
73
74
  }
74
75
 
76
+ function isAgentPresenceUser(user: CollabUser): boolean {
77
+ return (
78
+ user.email.trim().toLowerCase() ===
79
+ DEFAULT_AGENT_IDENTITY.email.trim().toLowerCase()
80
+ );
81
+ }
82
+
75
83
  /** Small presence avatar circle with hover card showing name + email */
76
84
  function PresenceAvatarTip({
77
85
  user,
@@ -143,6 +151,7 @@ function SortableSlideThumb({
143
151
  designSystem,
144
152
  onOverflowChange,
145
153
  readOnly = false,
154
+ aiEditing = false,
146
155
  }: {
147
156
  slide: Slide;
148
157
  index: number;
@@ -154,6 +163,7 @@ function SortableSlideThumb({
154
163
  aspectRatio?: AspectRatio;
155
164
  designSystem?: DesignSystemData;
156
165
  onOverflowChange: (info: SlideOverflowInfo) => void;
166
+ aiEditing?: boolean;
157
167
  }) {
158
168
  const t = useT();
159
169
  const {
@@ -175,6 +185,11 @@ function SortableSlideThumb({
175
185
  };
176
186
 
177
187
  const thumbDims = getAspectRatioDims(aspectRatio);
188
+ const agentPresent = presenceUsers.some(isAgentPresenceUser);
189
+ const humanPresenceUsers = presenceUsers.filter(
190
+ (user) => !isAgentPresenceUser(user),
191
+ );
192
+ const showAiMarker = aiEditing || agentPresent;
178
193
 
179
194
  return (
180
195
  <div ref={setNodeRef} style={style}>
@@ -189,7 +204,7 @@ function SortableSlideThumb({
189
204
  aria-current={isActive ? "true" : undefined}
190
205
  data-slide-thumbnail-id={slide.id}
191
206
  className={`w-full text-left flex items-start gap-1.5 p-1.5 rounded-lg transition-[background-color,box-shadow] duration-150 ${
192
- isActive ? "bg-accent ring-1 ring-primary/50" : ""
207
+ isActive ? "bg-accent" : ""
193
208
  } ${
194
209
  readOnly ? "" : "cursor-grab active:cursor-grabbing"
195
210
  } focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1`}
@@ -199,14 +214,17 @@ function SortableSlideThumb({
199
214
  <span className="block text-center text-[10px] font-medium leading-5 text-muted-foreground/70">
200
215
  {index + 1}
201
216
  </span>
202
- {presenceUsers.length > 0 && (
217
+ {(showAiMarker || humanPresenceUsers.length > 0) && (
203
218
  <div className="absolute left-1/2 top-5 z-10 flex -translate-x-1/2 flex-col items-center gap-1">
204
- {presenceUsers.slice(0, 4).map((u, i) => (
219
+ {showAiMarker && (
220
+ <AiEditingMarker className="size-4 text-[8px]" />
221
+ )}
222
+ {humanPresenceUsers.slice(0, 4).map((u, i) => (
205
223
  <PresenceAvatarTip key={i} user={u} size={14} />
206
224
  ))}
207
- {presenceUsers.length > 4 && (
225
+ {humanPresenceUsers.length > 4 && (
208
226
  <span className="flex h-4 min-w-4 items-center justify-center rounded-full bg-muted px-1 text-[8px] font-medium leading-none text-muted-foreground ring-1 ring-black/40">
209
- +{presenceUsers.length - 4}
227
+ +{humanPresenceUsers.length - 4}
210
228
  </span>
211
229
  )}
212
230
  </div>
@@ -224,8 +242,8 @@ function SortableSlideThumb({
224
242
  className="w-full overflow-hidden rounded border"
225
243
  style={{
226
244
  borderColor:
227
- presenceUsers.length > 0
228
- ? presenceUsers[0].color + "66"
245
+ humanPresenceUsers.length > 0
246
+ ? humanPresenceUsers[0].color + "66"
229
247
  : "rgba(255,255,255,0.06)",
230
248
  aspectRatio: `${thumbDims.width} / ${thumbDims.height}`,
231
249
  contentVisibility: "auto",
@@ -248,14 +266,12 @@ function GeneratingSlideSkeleton({
248
266
  index,
249
267
  aspectRatio,
250
268
  designSystem,
251
- content,
252
269
  selected,
253
270
  onSelect,
254
271
  }: {
255
272
  index: number;
256
273
  aspectRatio?: AspectRatio;
257
274
  designSystem?: DesignSystemData;
258
- content?: string | null;
259
275
  selected?: boolean;
260
276
  onSelect?: () => void;
261
277
  }) {
@@ -263,20 +279,20 @@ function GeneratingSlideSkeleton({
263
279
  return (
264
280
  <button
265
281
  type="button"
266
- className={`relative block w-full rounded-lg text-left transition-[box-shadow] ${
267
- selected ? "ring-1 ring-primary" : ""
282
+ className={`relative block w-full rounded-lg text-left transition-colors ${
283
+ selected ? "bg-accent" : ""
268
284
  }`}
269
285
  aria-label={t("editorSidebar.generatingSlide")}
270
286
  aria-current={selected ? "true" : undefined}
271
287
  onClick={onSelect}
272
288
  >
273
289
  <div className="flex w-full items-start gap-1.5 rounded-lg p-1.5">
274
- <span className="flex-shrink-0 w-4 text-center text-[10px] font-medium leading-5 text-muted-foreground/70">
290
+ <span className="flex flex-shrink-0 flex-col items-center gap-1 text-center text-[10px] font-medium leading-5 text-muted-foreground/70">
275
291
  {index + 1}
292
+ <AiEditingMarker className="size-4 text-[8px]" />
276
293
  </span>
277
294
  <div className="flex-1 min-w-0">
278
295
  <GeneratingSlidePreview
279
- content={content}
280
296
  aspectRatio={aspectRatio}
281
297
  designSystem={designSystem}
282
298
  thumbnail
@@ -302,7 +318,6 @@ export default function EditorSidebar({
302
318
  onSelectGeneratingSlide,
303
319
  }: EditorSidebarProps) {
304
320
  const slideButtonRefs = useRef(new Map<string, HTMLButtonElement>());
305
- const thumbScrollRef = useRef<HTMLDivElement>(null);
306
321
  const measurementsRef = useRef(
307
322
  new Map<
308
323
  string,
@@ -310,6 +325,12 @@ export default function EditorSidebar({
310
325
  >(),
311
326
  );
312
327
  const writeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
328
+ const aiEditedSlideIds = new Set(
329
+ (recentEdits ?? [])
330
+ .filter((edit) => edit.isAgent)
331
+ .map(slideIdFromEdit)
332
+ .filter((slideId): slideId is string => Boolean(slideId)),
333
+ );
313
334
 
314
335
  const writeDeckFitState = useCallback(() => {
315
336
  const currentSlideIds = new Set(slides.map((slide) => slide.id));
@@ -383,19 +404,6 @@ export default function EditorSidebar({
383
404
  };
384
405
  }, []);
385
406
 
386
- // Resolve a recent-edit descriptor (`slides.<id>`) to the on-screen rect of
387
- // that slide's thumbnail button, relative to the scroll container, so the
388
- // shared RecentEditHighlights overlay can draw a fading "AI edited" ring.
389
- const resolveThumbRect = useCallback(
390
- (edit: AttributedRecentEdit): DOMRect | null => {
391
- const slideId = slideIdFromEdit(edit);
392
- if (!slideId) return null;
393
- const node = slideButtonRefs.current.get(slideId);
394
- if (!node) return null;
395
- return node.getBoundingClientRect();
396
- },
397
- [],
398
- );
399
407
  const registerSlideButton = useCallback(
400
408
  (slideId: string, node: HTMLButtonElement | null) => {
401
409
  if (node) {
@@ -446,10 +454,7 @@ export default function EditorSidebar({
446
454
 
447
455
  return (
448
456
  <div className="flex h-full min-h-0 w-48 flex-shrink-0 flex-col bg-background sm:w-52">
449
- <div
450
- ref={thumbScrollRef}
451
- className="relative min-h-0 flex-1 space-y-1 overflow-y-auto overscroll-contain p-2"
452
- >
457
+ <div className="relative min-h-0 flex-1 space-y-1 overflow-y-auto overscroll-contain p-2">
453
458
  <SortableContext
454
459
  items={slides.map((s) => s.id)}
455
460
  strategy={verticalListSortingStrategy}
@@ -466,6 +471,7 @@ export default function EditorSidebar({
466
471
  presenceUsers={slidePresence?.get(slide.id) ?? []}
467
472
  aspectRatio={aspectRatio}
468
473
  designSystem={designSystem}
474
+ aiEditing={aiEditedSlideIds.has(slide.id)}
469
475
  onOverflowChange={(info) =>
470
476
  handleSlideOverflowChange(slide, info)
471
477
  }
@@ -477,21 +483,10 @@ export default function EditorSidebar({
477
483
  index={generatingSlide.index}
478
484
  aspectRatio={aspectRatio}
479
485
  designSystem={designSystem}
480
- content={generatingSlide.content}
481
486
  selected={generatingSlideSelected}
482
487
  onSelect={onSelectGeneratingSlide}
483
488
  />
484
489
  )}
485
- {/* Fading "AI edited" highlights over the thumbnails of just-edited
486
- slides (the component handles the fade + name/color tag). */}
487
- {recentEdits && recentEdits.length > 0 && (
488
- <RecentEditHighlights
489
- edits={recentEdits}
490
- resolveRect={resolveThumbRect}
491
- containerRef={thumbScrollRef}
492
- outlineOnly
493
- />
494
- )}
495
490
  </div>
496
491
  </div>
497
492
  );