@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.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/changelog/2026-08-10-wide-tables-in-analytics-chat-now-scroll-horizontally-within.md +6 -0
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +13 -53
- package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +11 -70
- package/corpus/templates/calendar/app/lib/event-layout.ts +122 -0
- package/corpus/templates/calendar/app/routes/_app.availability.tsx +1 -1
- package/corpus/templates/calendar/changelog/2026-08-10-opening-availability-settings-from-the-agent-lands-on-the-av.md +6 -0
- package/corpus/templates/calendar/vitest.config.ts +6 -1
- package/corpus/templates/clips/actions/lib/audio-only-transcription.ts +3 -1
- package/corpus/templates/clips/app/components/player/video-player.tsx +44 -4
- package/corpus/templates/clips/changelog/2026-08-10-fixed-transcription-failures-for-recordings-without-captured.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-10-mobile-clips-can-now-enter-fullscreen-on-iphone-safari.md +6 -0
- package/corpus/templates/content/actions/_property-utils.ts +11 -1
- package/corpus/templates/content/actions/view-screen.ts +62 -12
- package/corpus/templates/content/server/plugins/agent-chat.ts +2 -0
- package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +22 -0
- package/corpus/templates/design/AGENTS.md +3 -0
- package/corpus/templates/design/actions/create-design-from-template.ts +19 -0
- package/corpus/templates/design/actions/get-design-snapshot.ts +18 -0
- package/corpus/templates/design/actions/get-design-template.ts +178 -0
- package/corpus/templates/design/actions/list-designs.ts +3 -1
- package/corpus/templates/design/actions/view-screen.ts +31 -0
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +225 -214
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +3 -0
- package/corpus/templates/design/app/i18n-data.ts +78 -0
- package/corpus/templates/design/app/lib/design-authors.ts +64 -0
- package/corpus/templates/design/app/pages/Index.tsx +114 -4
- package/corpus/templates/design/app/pages/design-editor/generation-prompt-directives.ts +1 -0
- package/corpus/templates/design/changelog/2026-08-07-alt-dragging-an-element-to-copy-it-now-keeps-the-copy-visibl.md +6 -0
- package/corpus/templates/design/changelog/2026-08-07-design-cards-now-show-who-created-each-design-with-a-filter-.md +6 -0
- package/corpus/templates/design/changelog/2026-08-07-drawing-a-shape-on-the-overview-canvas-now-keeps-the-shape-s.md +6 -0
- package/corpus/templates/design/changelog/2026-08-07-hold-option-alt-while-dragging-a-frame-on-the-overview-canva.md +6 -0
- package/corpus/templates/design/server/lib/design-template-data.ts +143 -0
- package/corpus/templates/design/server/plugins/agent-chat.ts +1 -0
- package/corpus/templates/forms/AGENTS.md +2 -2
- package/corpus/templates/forms/actions/create-form.ts +1 -1
- package/corpus/templates/forms/actions/update-form.ts +5 -5
- package/corpus/templates/forms/changelog/2026-08-10-forms-now-keep-agent-applied-field-changes-visible-after-an-.md +6 -0
- package/corpus/templates/forms/changelog/2026-08-10-published-form-links-always-use-the-working-public-form-url.md +6 -0
- package/corpus/templates/forms/server/plugins/agent-chat.ts +2 -2
- package/corpus/templates/slides/AGENTS.md +4 -3
- package/corpus/templates/slides/actions/import-google-slides-reference.ts +56 -10
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +12 -74
- package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +5 -1
- package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +15 -4
- package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +116 -55
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +1 -0
- package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +143 -18
- package/corpus/templates/slides/app/i18n/en-US.ts +6 -0
- package/corpus/templates/slides/app/lib/generation-state.ts +7 -1
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +32 -14
- package/corpus/templates/slides/app/pages/Index.tsx +110 -128
- package/corpus/templates/slides/changelog/2026-08-10-imported-reference-decks-now-show-clear-success-and-stay-sel.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-pasted-google-slides-links-import-after-reconnecting-google-.md +6 -0
- package/corpus/templates/slides/server/handlers/google-docs-auth.ts +4 -0
- package/corpus/templates/slides/server/lib/google-docs-oauth.ts +13 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +1 -1
- package/dist/agent/engine/registry.d.ts +8 -2
- package/dist/agent/engine/registry.js +63 -29
- package/dist/agent/production-agent.js +7 -0
- package/dist/agent/run-manager.d.ts +5 -4
- package/dist/agent/run-manager.js +6 -5
- package/dist/cli/claude-code-participant.d.ts +2 -1
- package/dist/cli/claude-code-participant.js +19 -0
- package/dist/cli/code-agent-executor.js +223 -0
- package/dist/cli/create-workspace.js +3 -3
- package/dist/cli/create.d.ts +3 -2
- package/dist/cli/create.js +20 -24
- package/dist/client/AgentPanel.js +8 -4
- package/dist/client/AssistantChat.js +34 -1
- package/dist/client/agent-chat-adapter.d.ts +1 -0
- package/dist/client/agent-chat-adapter.js +10 -2
- package/dist/client/chat/markdown-renderer.d.ts +1 -0
- package/dist/client/chat/markdown-renderer.js +4 -0
- package/dist/client/chat/message-components.d.ts +1 -0
- package/dist/client/chat/message-components.js +9 -1
- package/dist/client/chat/tool-call-display.d.ts +8 -0
- package/dist/client/chat/tool-call-display.js +30 -21
- package/dist/client/chat-first/app-pane.js +1 -1
- package/dist/client/chat-first/apps-rail.d.ts +1 -1
- package/dist/client/chat-first/apps-rail.js +11 -11
- package/dist/client/chat-first/chat-history.d.ts +1 -1
- package/dist/client/chat-first/chat-history.js +2 -0
- package/dist/client/chat-first/primary-nav.js +1 -1
- package/dist/client/resources/McpConnectionSuggestion.js +2 -2
- package/dist/client/settings/SettingsTabsPage.d.ts +3 -1
- package/dist/client/settings/SettingsTabsPage.js +2 -2
- package/dist/client/sse-event-processor.d.ts +8 -0
- package/dist/client/sse-event-processor.js +13 -6
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.d.ts +5 -7
- package/dist/deploy/build.js +19 -12
- package/dist/localization/default-messages.js +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +6 -6
- package/dist/provider-api/actions/provider-api.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/scripts/call-agent.js +3 -3
- package/dist/scripts/describe-workspace-apps.js +1 -1
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/agent-capabilities.js +0 -0
- package/dist/server/agent-chat/context-tools.js +1 -1
- package/dist/server/agent-chat/prompt-resources.js +1 -1
- package/dist/server/agent-chat-plugin.js +1 -1
- package/dist/server/auth.js +190 -42
- package/dist/server/better-auth-migrations.js +53 -0
- package/dist/server/http-response-telemetry.js +7 -1
- package/dist/server/onboarding-html.js +119 -33
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/password-policy.d.ts +2 -1
- package/dist/shared/password-policy.js +2 -1
- package/dist/styles/agent-conversation.css +20 -3
- package/dist/styles/agent-native.css +14 -2
- package/dist/templates/default/app/i18n/ar-SA.ts +1 -1
- package/dist/templates/default/app/i18n/de-DE.ts +1 -1
- package/dist/templates/default/app/i18n/en-US.ts +1 -1
- package/dist/templates/default/app/i18n/es-ES.ts +1 -1
- package/dist/templates/default/app/i18n/fr-FR.ts +1 -1
- package/dist/templates/default/app/i18n/hi-IN.ts +1 -1
- package/dist/templates/default/app/i18n/ja-JP.ts +1 -1
- package/dist/templates/default/app/i18n/ko-KR.ts +1 -1
- package/dist/templates/default/app/i18n/pt-BR.ts +1 -1
- package/dist/templates/default/app/i18n/zh-CN.ts +1 -1
- package/dist/templates/default/app/i18n/zh-TW.ts +1 -1
- package/dist/templates/default/pnpm-workspace.yaml +1 -0
- package/dist/templates/workspace-root/pnpm-workspace.yaml +2 -0
- package/docs/content/cloneable-saas.mdx +6 -6
- package/docs/content/deployment.mdx +10 -8
- package/docs/content/environment-variables.mdx +4 -3
- package/package.json +2 -2
- package/src/templates/default/app/i18n/ar-SA.ts +1 -1
- package/src/templates/default/app/i18n/de-DE.ts +1 -1
- package/src/templates/default/app/i18n/en-US.ts +1 -1
- package/src/templates/default/app/i18n/es-ES.ts +1 -1
- package/src/templates/default/app/i18n/fr-FR.ts +1 -1
- package/src/templates/default/app/i18n/hi-IN.ts +1 -1
- package/src/templates/default/app/i18n/ja-JP.ts +1 -1
- package/src/templates/default/app/i18n/ko-KR.ts +1 -1
- package/src/templates/default/app/i18n/pt-BR.ts +1 -1
- package/src/templates/default/app/i18n/zh-CN.ts +1 -1
- package/src/templates/default/app/i18n/zh-TW.ts +1 -1
- package/src/templates/default/pnpm-workspace.yaml +1 -0
- package/src/templates/workspace-root/pnpm-workspace.yaml +2 -0
|
@@ -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
|
|
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
|
|
46
|
-
|
|
47
|
-
|
|
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
|
``.
|
|
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
|
|
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 =
|
|
160
|
-
|
|
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 =
|
|
164
|
-
|
|
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}
|
|
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-
|
|
208
|
-
}
|
|
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={`
|
|
317
|
-
selected ? "
|
|
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
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|