@agent-native/core 0.148.1 → 0.149.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 (145) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/changelog/2026-08-10-wide-tables-in-analytics-chat-now-scroll-horizontally-within.md +6 -0
  3. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +13 -53
  4. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +11 -70
  5. package/corpus/templates/calendar/app/lib/event-layout.ts +122 -0
  6. package/corpus/templates/calendar/app/routes/_app.availability.tsx +1 -1
  7. package/corpus/templates/calendar/changelog/2026-08-10-opening-availability-settings-from-the-agent-lands-on-the-av.md +6 -0
  8. package/corpus/templates/calendar/vitest.config.ts +6 -1
  9. package/corpus/templates/clips/actions/lib/audio-only-transcription.ts +3 -1
  10. package/corpus/templates/clips/app/components/player/video-player.tsx +44 -4
  11. package/corpus/templates/clips/changelog/2026-08-10-fixed-transcription-failures-for-recordings-without-captured.md +6 -0
  12. package/corpus/templates/clips/changelog/2026-08-10-mobile-clips-can-now-enter-fullscreen-on-iphone-safari.md +6 -0
  13. package/corpus/templates/content/actions/_property-utils.ts +11 -1
  14. package/corpus/templates/content/actions/view-screen.ts +62 -12
  15. package/corpus/templates/content/server/plugins/agent-chat.ts +2 -0
  16. package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +22 -0
  17. package/corpus/templates/design/AGENTS.md +3 -0
  18. package/corpus/templates/design/actions/create-design-from-template.ts +19 -0
  19. package/corpus/templates/design/actions/get-design-snapshot.ts +18 -0
  20. package/corpus/templates/design/actions/get-design-template.ts +178 -0
  21. package/corpus/templates/design/actions/list-designs.ts +3 -1
  22. package/corpus/templates/design/actions/view-screen.ts +31 -0
  23. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +225 -214
  24. package/corpus/templates/design/app/components/design/multi-screen/types.ts +3 -0
  25. package/corpus/templates/design/app/i18n-data.ts +78 -0
  26. package/corpus/templates/design/app/lib/design-authors.ts +64 -0
  27. package/corpus/templates/design/app/pages/Index.tsx +114 -4
  28. package/corpus/templates/design/app/pages/design-editor/generation-prompt-directives.ts +1 -0
  29. package/corpus/templates/design/changelog/2026-08-07-alt-dragging-an-element-to-copy-it-now-keeps-the-copy-visibl.md +6 -0
  30. package/corpus/templates/design/changelog/2026-08-07-design-cards-now-show-who-created-each-design-with-a-filter-.md +6 -0
  31. package/corpus/templates/design/changelog/2026-08-07-drawing-a-shape-on-the-overview-canvas-now-keeps-the-shape-s.md +6 -0
  32. package/corpus/templates/design/changelog/2026-08-07-hold-option-alt-while-dragging-a-frame-on-the-overview-canva.md +6 -0
  33. package/corpus/templates/design/server/lib/design-template-data.ts +143 -0
  34. package/corpus/templates/design/server/plugins/agent-chat.ts +1 -0
  35. package/corpus/templates/forms/AGENTS.md +2 -2
  36. package/corpus/templates/forms/actions/create-form.ts +1 -1
  37. package/corpus/templates/forms/actions/update-form.ts +5 -5
  38. package/corpus/templates/forms/changelog/2026-08-10-forms-now-keep-agent-applied-field-changes-visible-after-an-.md +6 -0
  39. package/corpus/templates/forms/changelog/2026-08-10-published-form-links-always-use-the-working-public-form-url.md +6 -0
  40. package/corpus/templates/forms/server/plugins/agent-chat.ts +2 -2
  41. package/corpus/templates/slides/AGENTS.md +4 -3
  42. package/corpus/templates/slides/actions/import-google-slides-reference.ts +56 -10
  43. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +12 -74
  44. package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +5 -1
  45. package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +15 -4
  46. package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +116 -55
  47. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +1 -0
  48. package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +143 -18
  49. package/corpus/templates/slides/app/i18n/en-US.ts +6 -0
  50. package/corpus/templates/slides/app/lib/generation-state.ts +7 -1
  51. package/corpus/templates/slides/app/pages/DeckEditor.tsx +32 -14
  52. package/corpus/templates/slides/app/pages/Index.tsx +110 -128
  53. package/corpus/templates/slides/changelog/2026-08-10-imported-reference-decks-now-show-clear-success-and-stay-sel.md +6 -0
  54. package/corpus/templates/slides/changelog/2026-08-10-pasted-google-slides-links-import-after-reconnecting-google-.md +6 -0
  55. package/corpus/templates/slides/server/handlers/google-docs-auth.ts +4 -0
  56. package/corpus/templates/slides/server/lib/google-docs-oauth.ts +13 -0
  57. package/corpus/templates/slides/server/plugins/agent-chat.ts +1 -1
  58. package/dist/agent/engine/registry.d.ts +8 -2
  59. package/dist/agent/engine/registry.js +63 -29
  60. package/dist/agent/production-agent.js +7 -0
  61. package/dist/agent/run-manager.d.ts +5 -4
  62. package/dist/agent/run-manager.js +6 -5
  63. package/dist/cli/claude-code-participant.d.ts +2 -1
  64. package/dist/cli/claude-code-participant.js +19 -0
  65. package/dist/cli/code-agent-executor.js +223 -0
  66. package/dist/cli/create-workspace.js +3 -3
  67. package/dist/cli/create.d.ts +3 -2
  68. package/dist/cli/create.js +20 -24
  69. package/dist/client/AgentPanel.js +8 -4
  70. package/dist/client/AssistantChat.js +34 -1
  71. package/dist/client/agent-chat-adapter.d.ts +1 -0
  72. package/dist/client/agent-chat-adapter.js +10 -2
  73. package/dist/client/chat/markdown-renderer.d.ts +1 -0
  74. package/dist/client/chat/markdown-renderer.js +4 -0
  75. package/dist/client/chat/message-components.d.ts +1 -0
  76. package/dist/client/chat/message-components.js +9 -1
  77. package/dist/client/chat/tool-call-display.d.ts +8 -0
  78. package/dist/client/chat/tool-call-display.js +30 -21
  79. package/dist/client/chat-first/app-pane.js +1 -1
  80. package/dist/client/chat-first/apps-rail.d.ts +1 -1
  81. package/dist/client/chat-first/apps-rail.js +11 -11
  82. package/dist/client/chat-first/chat-history.d.ts +1 -1
  83. package/dist/client/chat-first/chat-history.js +2 -0
  84. package/dist/client/chat-first/primary-nav.js +1 -1
  85. package/dist/client/resources/McpConnectionSuggestion.js +2 -2
  86. package/dist/client/settings/SettingsTabsPage.d.ts +3 -1
  87. package/dist/client/settings/SettingsTabsPage.js +2 -2
  88. package/dist/client/sse-event-processor.d.ts +8 -0
  89. package/dist/client/sse-event-processor.js +13 -6
  90. package/dist/collab/awareness.d.ts +2 -2
  91. package/dist/collab/routes.d.ts +1 -1
  92. package/dist/collab/struct-routes.d.ts +1 -1
  93. package/dist/deploy/build.d.ts +5 -7
  94. package/dist/deploy/build.js +19 -12
  95. package/dist/localization/default-messages.js +1 -1
  96. package/dist/notifications/routes.d.ts +3 -3
  97. package/dist/observability/routes.d.ts +6 -6
  98. package/dist/provider-api/actions/provider-api.d.ts +2 -2
  99. package/dist/resources/handlers.d.ts +1 -1
  100. package/dist/scripts/call-agent.js +3 -3
  101. package/dist/scripts/describe-workspace-apps.js +1 -1
  102. package/dist/secrets/routes.d.ts +3 -3
  103. package/dist/server/agent-capabilities.js +0 -0
  104. package/dist/server/agent-chat/context-tools.js +1 -1
  105. package/dist/server/agent-chat/prompt-resources.js +1 -1
  106. package/dist/server/agent-chat-plugin.js +1 -1
  107. package/dist/server/auth.js +190 -42
  108. package/dist/server/better-auth-migrations.js +53 -0
  109. package/dist/server/http-response-telemetry.js +7 -1
  110. package/dist/server/onboarding-html.js +119 -33
  111. package/dist/server/transcribe-voice.d.ts +1 -1
  112. package/dist/shared/password-policy.d.ts +2 -1
  113. package/dist/shared/password-policy.js +2 -1
  114. package/dist/styles/agent-conversation.css +20 -3
  115. package/dist/styles/agent-native.css +14 -2
  116. package/dist/templates/default/app/i18n/ar-SA.ts +1 -1
  117. package/dist/templates/default/app/i18n/de-DE.ts +1 -1
  118. package/dist/templates/default/app/i18n/en-US.ts +1 -1
  119. package/dist/templates/default/app/i18n/es-ES.ts +1 -1
  120. package/dist/templates/default/app/i18n/fr-FR.ts +1 -1
  121. package/dist/templates/default/app/i18n/hi-IN.ts +1 -1
  122. package/dist/templates/default/app/i18n/ja-JP.ts +1 -1
  123. package/dist/templates/default/app/i18n/ko-KR.ts +1 -1
  124. package/dist/templates/default/app/i18n/pt-BR.ts +1 -1
  125. package/dist/templates/default/app/i18n/zh-CN.ts +1 -1
  126. package/dist/templates/default/app/i18n/zh-TW.ts +1 -1
  127. package/dist/templates/default/pnpm-workspace.yaml +1 -0
  128. package/dist/templates/workspace-root/pnpm-workspace.yaml +2 -0
  129. package/docs/content/cloneable-saas.mdx +6 -6
  130. package/docs/content/deployment.mdx +10 -8
  131. package/docs/content/environment-variables.mdx +4 -3
  132. package/package.json +2 -2
  133. package/src/templates/default/app/i18n/ar-SA.ts +1 -1
  134. package/src/templates/default/app/i18n/de-DE.ts +1 -1
  135. package/src/templates/default/app/i18n/en-US.ts +1 -1
  136. package/src/templates/default/app/i18n/es-ES.ts +1 -1
  137. package/src/templates/default/app/i18n/fr-FR.ts +1 -1
  138. package/src/templates/default/app/i18n/hi-IN.ts +1 -1
  139. package/src/templates/default/app/i18n/ja-JP.ts +1 -1
  140. package/src/templates/default/app/i18n/ko-KR.ts +1 -1
  141. package/src/templates/default/app/i18n/pt-BR.ts +1 -1
  142. package/src/templates/default/app/i18n/zh-CN.ts +1 -1
  143. package/src/templates/default/app/i18n/zh-TW.ts +1 -1
  144. package/src/templates/default/pnpm-workspace.yaml +1 -0
  145. package/src/templates/workspace-root/pnpm-workspace.yaml +2 -0
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-10
4
+ ---
5
+
6
+ Published form links always use the working public form URL
@@ -6,7 +6,7 @@ import {
6
6
 
7
7
  import actionsRegistry from "../../.generated/actions-registry.js";
8
8
 
9
- const FORMS_SYSTEM_PROMPT = `## Agent-Native Forms
9
+ export const FORMS_SYSTEM_PROMPT = `## Agent-Native Forms
10
10
 
11
11
  You are the in-app agent for Agent-Native Forms, a chat-first form builder and response workspace. Help users create, edit, publish, share, and analyze forms through the registered actions. The actions are the source of truth for database access, validation, permissions, UI sync, and business logic.
12
12
 
@@ -15,7 +15,7 @@ Core rules:
15
15
  - Never hardcode API keys, tokens, webhook URLs, signing secrets, customer data, or credential-looking literals. Use placeholders or configured integrations.
16
16
  - Inspect the current state when it matters. Use \`view-screen\` when the active form, selected field, publish state, response table, or current route is unclear.
17
17
  - Use \`create-form\`, \`update-form\`, \`patch-form-fields\`, \`restore-form\`, and \`delete-form\` for form lifecycle and field edits. Treat \`delete-form\` as Archive by default; pass \`purge: true\` only when the user explicitly asks to permanently delete a form and its responses.
18
- - When the user asks for an anonymous feedback form or survey, create the complete form atomically with \`status: "published"\`, then return the exact public response URL from the successful action result. Do not return only the private editor link, and do not claim it is live without verifying the saved status and fields.
18
+ - When a form is published, copy the returned \`publicUrl\` verbatim into the response. Never derive a public link from \`slug\` or remove the \`/f/\` path segment; if \`publicUrl\` is missing, say that the public link could not be retrieved instead of inventing one. For an anonymous feedback form or survey, create the complete form atomically with \`status: "published"\` and verify the saved status and fields before claiming it is live.
19
19
  - Use \`preview-form\` when the user asks about a form's setup, fields, configuration, publish state, or response count. It renders a native chat summary.
20
20
  - When the user wants email notifications for new responses, set \`settings.emailOnNewResponses: true\` with \`create-form\` or \`update-form\`; notifications go to the form owner's account email.
21
21
  - When the user wants Slack, Discord, Google Sheets, or webhook notifications, explain that Forms already supports them from the form builder's Integrations tab and navigate there with \`tab: "integrations"\`. Slack uses an Incoming Webhook URL; Google Sheets uses a deployed Apps Script \`/exec\` URL that parses \`e.postData.contents\`. Do not redirect users to the managed Messaging integration for these form destinations.
@@ -42,9 +42,10 @@ Before building common workspace or agent UI, read `agent-native-toolkit` and
42
42
  Drive API needs, use `provider-api-catalog`, `provider-api-docs`, and
43
43
  `provider-api-request`; auth comes from the user's Google Docs OAuth. Stage
44
44
  large scans with `stageAs` and analyze them via `query-staged-dataset`.
45
- - `import-google-slides-reference` accepts either a Picker `fileId` or a
46
- `presentationUrl`; imported PPTX timing metadata, including by-paragraph
47
- reveals, stays on the deck's slide records.
45
+ - `import-google-slides-reference` accepts a Picker `fileId` or `presentationUrl`;
46
+ pasted URLs may need a one-time Google reconnect for Drive export. Preserve
47
+ imported PPTX timing metadata, including by-paragraph reveals, on slide
48
+ records.
48
49
  - For images, use `generate-image-api` with provenance; show results as
49
50
  `![alt](url)`.
50
51
  - Ask a sibling app's agent with a natural-language `call-agent` message by
@@ -64,9 +64,7 @@ async function exportGoogleSlidesAsPptx(fileId: string, accessToken: string) {
64
64
  );
65
65
 
66
66
  if (!response.ok) {
67
- throw new Error(
68
- `Google Drive returned HTTP ${response.status} while exporting the presentation.`,
69
- );
67
+ throw googleSlidesExportError(response.status);
70
68
  }
71
69
 
72
70
  const bytes = await response.arrayBuffer();
@@ -80,14 +78,15 @@ async function exportGoogleSlidesAsPptx(fileId: string, accessToken: string) {
80
78
  interface GoogleSlidesImageElement {
81
79
  objectId?: string;
82
80
  size?: {
83
- width?: { magnitude?: number };
84
- height?: { magnitude?: number };
81
+ width?: { magnitude?: number; unit?: string };
82
+ height?: { magnitude?: number; unit?: string };
85
83
  };
86
84
  transform?: {
87
85
  scaleX?: number;
88
86
  scaleY?: number;
89
87
  translateX?: number;
90
88
  translateY?: number;
89
+ unit?: string;
91
90
  };
92
91
  image?: {
93
92
  contentUrl?: string;
@@ -124,6 +123,41 @@ function finiteNumber(value: number | undefined): number | undefined {
124
123
  return value != null && Number.isFinite(value) ? value : undefined;
125
124
  }
126
125
 
126
+ const EMU_PER_POINT = 12700;
127
+
128
+ export function googleSlidesMeasurementToEmu(
129
+ value: number | undefined,
130
+ unit?: string,
131
+ ): number | undefined {
132
+ const finite = finiteNumber(value);
133
+ if (finite == null) return undefined;
134
+ return unit === "PT" ? finite * EMU_PER_POINT : finite;
135
+ }
136
+
137
+ export function googleSlidesExportError(
138
+ status: number,
139
+ ): Error & { statusCode?: number } {
140
+ if (status === 401) {
141
+ return Object.assign(
142
+ new Error(
143
+ "Google Drive connection expired. Connect Google again, then retry the import.",
144
+ ),
145
+ { statusCode: 401 },
146
+ );
147
+ }
148
+ if (status === 403) {
149
+ return Object.assign(
150
+ new Error(
151
+ "Google Drive denied access to this presentation. Connect Google again to enable pasted-link imports, or choose the deck through Google Picker.",
152
+ ),
153
+ { statusCode: 403 },
154
+ );
155
+ }
156
+ return new Error(
157
+ `Google Drive returned HTTP ${status} while exporting the presentation.`,
158
+ );
159
+ }
160
+
127
161
  /**
128
162
  * Google Drive's PPTX export can omit image page elements that remain present
129
163
  * in the native Slides document. Read those native objects as a fidelity
@@ -135,7 +169,7 @@ async function fetchGoogleSlidesImageFallbacks(
135
169
  presentation: ParsedPresentation,
136
170
  ): Promise<ImportedImageFallback[]> {
137
171
  const fields =
138
- "slides(pageElements(objectId,size,transform,image(contentUrl,imageProperties(cropProperties))))";
172
+ "slides(pageElements(objectId,size(width(magnitude,unit),height(magnitude,unit)),transform(scaleX,scaleY,translateX,translateY,unit),image(contentUrl,imageProperties(cropProperties))))";
139
173
  const response = await ssrfSafeFetch(
140
174
  `https://slides.googleapis.com/v1/presentations/${encodeURIComponent(fileId)}?fields=${encodeURIComponent(fields)}`,
141
175
  { headers: { Authorization: `Bearer ${accessToken}` } },
@@ -156,12 +190,24 @@ async function fetchGoogleSlidesImageFallbacks(
156
190
  ).entries()) {
157
191
  for (const [imageIndex, element] of (slide.pageElements ?? []).entries()) {
158
192
  const contentUrl = element.image?.contentUrl;
159
- const baseWidth = finiteNumber(element.size?.width?.magnitude);
160
- const baseHeight = finiteNumber(element.size?.height?.magnitude);
193
+ const baseWidth = googleSlidesMeasurementToEmu(
194
+ element.size?.width?.magnitude,
195
+ element.size?.width?.unit,
196
+ );
197
+ const baseHeight = googleSlidesMeasurementToEmu(
198
+ element.size?.height?.magnitude,
199
+ element.size?.height?.unit,
200
+ );
161
201
  const scaleX = finiteNumber(element.transform?.scaleX) ?? 1;
162
202
  const scaleY = finiteNumber(element.transform?.scaleY) ?? 1;
163
- const x = finiteNumber(element.transform?.translateX);
164
- const y = finiteNumber(element.transform?.translateY);
203
+ const x = googleSlidesMeasurementToEmu(
204
+ element.transform?.translateX,
205
+ element.transform?.unit,
206
+ );
207
+ const y = googleSlidesMeasurementToEmu(
208
+ element.transform?.translateY,
209
+ element.transform?.unit,
210
+ );
165
211
  const width =
166
212
  baseWidth == null ? undefined : baseWidth * Math.abs(scaleX);
167
213
  const height =
@@ -17,12 +17,6 @@ import {
17
17
  import { CSS } from "@dnd-kit/utilities";
18
18
  import { appStateKeyForBrowserTab } from "@shared/app-state-tabs";
19
19
  import { hashSlideContent, type DeckFitState } from "@shared/slide-fit";
20
- import {
21
- IconGripVertical,
22
- IconCopy,
23
- IconTrash,
24
- IconLoader2,
25
- } from "@tabler/icons-react";
26
20
  import { useRef, useEffect } from "react";
27
21
  import { useCallback } from "react";
28
22
 
@@ -45,8 +39,6 @@ interface EditorSidebarProps {
45
39
  activeSlideId: string;
46
40
  deckId: string;
47
41
  onSelectSlide: (id: string) => void;
48
- onDuplicateSlide: (id: string) => void;
49
- onDeleteSlide: (id: string) => void;
50
42
  /** Viewer-role decks get thumbnails only: no add, duplicate, or delete. */
51
43
  readOnly?: boolean;
52
44
  /** Presence map: slideId → list of users currently viewing that slide */
@@ -140,21 +132,11 @@ function PresenceAvatarTip({
140
132
  );
141
133
  }
142
134
 
143
- // Thumbnail overlays sit on top of rendered slide artwork, which is arbitrary
144
- // user content rather than a themed app surface, so they use a fixed scrim.
145
- const THUMB_OVERLAY_CLASS =
146
- // guard:allow-raw-color — matches the duplicate/delete overlays below.
147
- "absolute top-2 left-7 rounded bg-black/60 p-1 backdrop-blur-sm border border-white/10 cursor-grab active:cursor-grabbing sm:opacity-0 sm:group-hover:opacity-100";
148
- // guard:allow-raw-color — same fixed scrim as the class above.
149
- const THUMB_OVERLAY_ICON_CLASS = "w-3 h-3 text-white/60";
150
-
151
135
  function SortableSlideThumb({
152
136
  slide,
153
137
  index,
154
138
  isActive,
155
139
  onSelect,
156
- onDuplicate,
157
- onDelete,
158
140
  registerButtonRef,
159
141
  presenceUsers = [],
160
142
  aspectRatio,
@@ -166,8 +148,6 @@ function SortableSlideThumb({
166
148
  index: number;
167
149
  isActive: boolean;
168
150
  onSelect: () => void;
169
- onDuplicate: () => void;
170
- onDelete: () => void;
171
151
  readOnly?: boolean;
172
152
  registerButtonRef: (slideId: string, node: HTMLButtonElement | null) => void;
173
153
  presenceUsers?: CollabUser[];
@@ -195,17 +175,22 @@ function SortableSlideThumb({
195
175
  };
196
176
 
197
177
  return (
198
- <div ref={setNodeRef} style={style} className="group relative">
178
+ <div ref={setNodeRef} style={style}>
199
179
  <button
200
180
  ref={(node) => registerButtonRef(slide.id, node)}
181
+ type="button"
182
+ {...(readOnly ? {} : attributes)}
183
+ {...(readOnly ? {} : listeners)}
201
184
  onClick={onSelect}
202
185
  onFocus={onSelect}
203
186
  aria-label={t("editorSidebar.selectSlide", { number: index + 1 })}
204
187
  aria-current={isActive ? "true" : undefined}
205
188
  data-slide-thumbnail-id={slide.id}
206
189
  className={`w-full text-left flex items-start gap-1.5 p-1.5 rounded-lg transition-[background-color,box-shadow] duration-150 ${
207
- isActive ? "bg-accent ring-1 ring-[#609FF8]/50" : "hover:bg-accent"
208
- } focus:outline-none`}
190
+ isActive ? "bg-accent ring-1 ring-primary/50" : ""
191
+ } ${
192
+ readOnly ? "" : "cursor-grab active:cursor-grabbing"
193
+ } focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1`}
209
194
  >
210
195
  {/* Index and slide presence share the fixed rail so presence does not resize the row. */}
211
196
  <div className="relative flex-shrink-0 w-4 self-stretch">
@@ -246,50 +231,6 @@ function SortableSlideThumb({
246
231
  </div>
247
232
  </div>
248
233
  </button>
249
-
250
- {/* Drag handle — overlaid on the thumbnail instead of holding its own
251
- * column, so the rail stays narrow. Mirrors the action buttons opposite. */}
252
- {!readOnly && (
253
- <div {...attributes} {...listeners} className={THUMB_OVERLAY_CLASS}>
254
- <IconGripVertical className={THUMB_OVERLAY_ICON_CLASS} />
255
- </div>
256
- )}
257
-
258
- {/* Actions - always visible on touch devices */}
259
- {!readOnly && (
260
- <div className="absolute top-2 right-2 flex gap-0.5 sm:opacity-0 sm:group-hover:opacity-100">
261
- <Tooltip>
262
- <TooltipTrigger asChild>
263
- <button
264
- onClick={(e) => {
265
- e.stopPropagation();
266
- onDuplicate();
267
- }}
268
- className="p-1.5 rounded bg-black/60 backdrop-blur-sm border border-white/10 hover:bg-black/80"
269
- aria-label={t("editorSidebar.duplicateSlide")}
270
- >
271
- <IconCopy className="w-3 h-3 text-white/60" />
272
- </button>
273
- </TooltipTrigger>
274
- <TooltipContent>{t("editorSidebar.duplicate")}</TooltipContent>
275
- </Tooltip>
276
- <Tooltip>
277
- <TooltipTrigger asChild>
278
- <button
279
- onClick={(e) => {
280
- e.stopPropagation();
281
- onDelete();
282
- }}
283
- className="p-1.5 rounded bg-black/60 backdrop-blur-sm border border-white/10 hover:bg-red-900/80"
284
- aria-label={t("editorSidebar.deleteSlide")}
285
- >
286
- <IconTrash className="w-3 h-3 text-white/60" />
287
- </button>
288
- </TooltipTrigger>
289
- <TooltipContent>{t("editorSidebar.delete")}</TooltipContent>
290
- </Tooltip>
291
- </div>
292
- )}
293
234
  </div>
294
235
  );
295
236
  }
@@ -313,14 +254,14 @@ function GeneratingSlideSkeleton({
313
254
  return (
314
255
  <button
315
256
  type="button"
316
- className={`group relative block w-full rounded-lg text-left transition-colors ${
317
- selected ? "bg-accent ring-1 ring-ring" : "hover:bg-accent/50"
257
+ className={`relative block w-full rounded-lg text-left transition-[box-shadow] ${
258
+ selected ? "ring-1 ring-primary" : ""
318
259
  }`}
319
260
  aria-label={t("editorSidebar.generatingSlide")}
320
261
  aria-current={selected ? "true" : undefined}
321
262
  onClick={onSelect}
322
263
  >
323
- <div className="w-full flex items-start gap-1.5 p-1.5 rounded-lg bg-accent/30">
264
+ <div className="flex w-full items-start gap-1.5 rounded-lg p-1.5">
324
265
  <span className="flex-shrink-0 w-4 text-center text-[10px] font-medium leading-5 text-muted-foreground/70">
325
266
  {index + 1}
326
267
  </span>
@@ -342,8 +283,6 @@ export default function EditorSidebar({
342
283
  activeSlideId,
343
284
  deckId,
344
285
  onSelectSlide,
345
- onDuplicateSlide,
346
- onDeleteSlide,
347
286
  readOnly = false,
348
287
  slidePresence,
349
288
  recentEdits,
@@ -513,8 +452,6 @@ export default function EditorSidebar({
513
452
  index={index}
514
453
  isActive={slide.id === activeSlideId}
515
454
  onSelect={() => onSelectSlide(slide.id)}
516
- onDuplicate={() => onDuplicateSlide(slide.id)}
517
- onDelete={() => onDeleteSlide(slide.id)}
518
455
  readOnly={readOnly}
519
456
  registerButtonRef={registerSlideButton}
520
457
  presenceUsers={slidePresence?.get(slide.id) ?? []}
@@ -543,6 +480,7 @@ export default function EditorSidebar({
543
480
  edits={recentEdits}
544
481
  resolveRect={resolveThumbRect}
545
482
  containerRef={thumbScrollRef}
483
+ outlineOnly
546
484
  />
547
485
  )}
548
486
  </div>
@@ -47,7 +47,7 @@ export default function GeneratingSlidePreview({
47
47
  return (
48
48
  <div
49
49
  className={cn(
50
- "relative w-full overflow-hidden rounded-lg border border-border/60 bg-muted/20",
50
+ "relative w-full overflow-hidden rounded-lg border border-primary/70",
51
51
  className,
52
52
  )}
53
53
  style={{ aspectRatio: cssRatio }}
@@ -65,6 +65,10 @@ export default function GeneratingSlidePreview({
65
65
  ) : (
66
66
  <SlideLoadingArtwork />
67
67
  )}
68
+ <div className="pointer-events-none absolute left-2 top-2 inline-flex items-center gap-1.5 rounded-full border border-primary/70 px-2 py-1 text-[11px] font-medium text-primary">
69
+ <span className="size-1.5 rounded-full bg-primary" aria-hidden="true" />
70
+ {t("raw.aiEditing")}
71
+ </div>
68
72
  <div className="absolute right-2 top-2 rounded-full bg-background/80 p-1.5 text-muted-foreground shadow-sm backdrop-blur">
69
73
  <IconLoader2 className="size-3.5 animate-spin" aria-hidden="true" />
70
74
  </div>
@@ -13,6 +13,7 @@ import { Button } from "../ui/button";
13
13
  interface GoogleDocsStatus {
14
14
  configured: boolean;
15
15
  connected: boolean;
16
+ googleSlidesUrlImportReady?: boolean;
16
17
  error?: string;
17
18
  message?: string;
18
19
  }
@@ -97,6 +98,12 @@ export function GoogleDriveConnectionCta({
97
98
  void refreshStatus();
98
99
  }, [refreshStatus]);
99
100
 
101
+ const needsReconnect =
102
+ status?.connected === true && status.googleSlidesUrlImportReady === false;
103
+ const requiresUrlImportAccess =
104
+ status?.connected === true ||
105
+ status?.googleSlidesUrlImportReady !== undefined;
106
+
100
107
  const connect = useCallback(async () => {
101
108
  if (connecting) return;
102
109
  setConnecting(true);
@@ -138,23 +145,27 @@ export function GoogleDriveConnectionCta({
138
145
  while (Date.now() < deadline && !popup.closed) {
139
146
  await new Promise((resolve) => window.setTimeout(resolve, 1_200));
140
147
  const next = await refreshStatus();
141
- if (next?.connected) {
148
+ const nextHasUrlImportAccess =
149
+ next?.googleSlidesUrlImportReady ?? !requiresUrlImportAccess;
150
+ if (next?.connected && nextHasUrlImportAccess) {
142
151
  popup.close();
143
152
  onConnected?.();
144
153
  return;
145
154
  }
146
155
  }
147
156
  const next = await refreshStatus();
148
- if (next?.connected) onConnected?.();
157
+ const nextHasUrlImportAccess =
158
+ next?.googleSlidesUrlImportReady ?? !requiresUrlImportAccess;
159
+ if (next?.connected && nextHasUrlImportAccess) onConnected?.();
149
160
  } catch (caught) {
150
161
  popup?.close();
151
162
  setError(caught instanceof Error ? caught.message : String(caught));
152
163
  } finally {
153
164
  setConnecting(false);
154
165
  }
155
- }, [connecting, onConnected, refreshStatus]);
166
+ }, [connecting, onConnected, refreshStatus, requiresUrlImportAccess]);
156
167
 
157
- if (loading || status?.connected) return null;
168
+ if (loading || (status?.connected && !needsReconnect)) return null;
158
169
 
159
170
  const displayStatus = status ?? { configured: false, connected: false };
160
171