@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.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +83 -45
- package/corpus/templates/analytics/changelog/2026-08-11-analytics-dashboards-keep-the-selected-tab-when-reopened.md +6 -0
- package/corpus/templates/assets/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/corpus/templates/brain/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/corpus/templates/calendar/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/corpus/templates/chat/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/corpus/templates/clips/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/corpus/templates/clips/changelog/2026-08-11-clips-recovers-from-transient-updates-and-avoids-macos-priva.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +32 -176
- package/corpus/templates/clips/desktop/src/lib/updater.ts +52 -37
- package/corpus/templates/clips/desktop/src/styles.css +1 -166
- package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +2 -0
- package/corpus/templates/clips/desktop/src-tauri/Info.plist +4 -0
- package/corpus/templates/clips/desktop/src-tauri/src/eventkit.rs +28 -1
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +2 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_speech.rs +48 -1
- package/corpus/templates/clips/desktop/src-tauri/src/permission_status.rs +3 -0
- package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +5 -0
- package/corpus/templates/clips/server/plugins/auth.ts +3 -0
- package/corpus/templates/content/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +2 -1
- package/corpus/templates/design/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +5 -1
- package/corpus/templates/design/changelog/2026-08-11-design-agent-prompts-copy-successfully-in-the-desktop-app.md +6 -0
- package/corpus/templates/dispatch/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/corpus/templates/dispatch/app/global.css +2 -2
- package/corpus/templates/dispatch/changelog/2026-08-10-desktop-workspace-sign-in.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-08-11-dispatch-no-longer-shows-the-generic-chat-starter-among-defa.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-08-11-integrations-and-scheduled-work-open-as-first-class-electron-pages.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-08-11-sidebar-text-is-slightly-easier-to-read-in-light-and-dark-mo.md +6 -0
- package/corpus/templates/dispatch/server/lib/identity-sso.ts +57 -31
- package/corpus/templates/dispatch/server/plugins/feature-flags.ts +5 -0
- package/corpus/templates/dispatch/server/plugins/identity-sso.ts +82 -33
- package/corpus/templates/dispatch/server/plugins/org-apps-directory.ts +7 -15
- package/corpus/templates/dispatch/server/plugins/setup-dispatch.ts +9 -1
- package/corpus/templates/dispatch/shared/feature-flags.ts +15 -0
- package/corpus/templates/factory/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/corpus/templates/forms/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/corpus/templates/forms/.agents/skills/form-responses/SKILL.md +8 -7
- package/corpus/templates/forms/app/hooks/use-forms.ts +8 -1
- package/corpus/templates/forms/changelog/2026-08-11-fixed-public-forms-so-conditional-questions-stay-hidden-unti.md +6 -0
- package/corpus/templates/forms/server/handlers/submissions.ts +2 -1
- package/corpus/templates/forms/server/lib/public-form-ssr.ts +37 -2
- package/corpus/templates/forms/shared/page-url.ts +54 -0
- package/corpus/templates/forms/shared/types.ts +4 -4
- package/corpus/templates/mail/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/corpus/templates/plan/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/corpus/templates/slides/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/corpus/templates/slides/actions/add-slide.ts +0 -27
- package/corpus/templates/slides/actions/import-file.ts +28 -9
- package/corpus/templates/slides/actions/view-screen.ts +4 -4
- package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +8 -1
- package/corpus/templates/slides/app/components/editor/AiEditingMarker.tsx +25 -0
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +43 -48
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +9 -6
- package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +1 -9
- package/corpus/templates/slides/app/components/editor/ImageDropPromptPopover.tsx +2 -0
- package/corpus/templates/slides/app/components/editor/ImageOverlay.tsx +0 -26
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +904 -217
- package/corpus/templates/slides/app/components/editor/canvas-interactions/index.ts +1 -0
- package/corpus/templates/slides/app/components/editor/canvas-interactions/slides-canvas-adapter.ts +11 -0
- package/corpus/templates/slides/app/components/editor/inline-edit-session.ts +16 -0
- package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +22 -6
- package/corpus/templates/slides/app/components/editor/style-clipboard.ts +61 -0
- package/corpus/templates/slides/app/components/layout/Layout.tsx +26 -2
- package/corpus/templates/slides/app/components/ui/context-menu.tsx +1 -0
- package/corpus/templates/slides/app/context/DeckContext.tsx +75 -25
- package/corpus/templates/slides/app/global.css +1 -1
- package/corpus/templates/slides/app/i18n/en-US.ts +6 -1
- package/corpus/templates/slides/app/lib/create-deck-generation.ts +1 -1
- package/corpus/templates/slides/app/lib/image-drop-to-agent.ts +12 -12
- package/corpus/templates/slides/app/lib/slide-agent-context.ts +52 -0
- package/corpus/templates/slides/app/lib/slide-image-replacement.ts +61 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +55 -116
- package/corpus/templates/slides/app/pages/Index.tsx +28 -3
- package/corpus/templates/slides/changelog/2026-08-11-agent-chat-now-follows-the-current-slide-and-selection.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-11-deck-titles-now-use-slide-content-instead-of-placeholder-filenames.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-11-history-now-opens-from-the-deck-overflow-menu.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-11-images-can-be-dropped-directly-onto-slides-and-resized-or-re.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-11-new-decks-now-transition-directly-to-a-full-page-editor-load.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-11-selected-slide-layers-can-be-nudged-precisely-with-arrow-key.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-11-slides-can-copy-and-paste-element-styles-with-cmd-option-c-v.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-11-slides-shows-one-compact-ai-marker-and-keeps-your-current-sl.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-11-text-edits-preserve-slide-layout-when-no-changes-are-made.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-11-the-style-toolbar-now-uses-the-full-available-width.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-11-undo-and-redo-now-move-one-visible-slide-state-at-a-time.md +6 -0
- package/corpus/templates/slides/server/lib/chat-attachments.ts +6 -2
- package/corpus/templates/slides/shared/deck-title.ts +56 -0
- package/corpus/templates/tasks/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/dist/agent/run-store.js +6 -1
- package/dist/agent/thread-data-builder.js +19 -44
- package/dist/agent/types.d.ts +13 -0
- package/dist/catalog.json +1 -0
- package/dist/cli/create.js +1 -1
- package/dist/client/AssistantChat.js +58 -10
- package/dist/client/FileStorageSetupCard.d.ts +6 -0
- package/dist/client/FileStorageSetupCard.js +18 -0
- package/dist/client/NewWorkspaceAppFlow.js +10 -0
- package/dist/client/agent-chat-adapter.js +87 -11
- package/dist/client/agent-chat.d.ts +1 -1
- package/dist/client/agent-chat.js +8 -3
- package/dist/client/chat/attachment-adapters.js +10 -0
- package/dist/client/chat/message-components.d.ts +4 -1
- package/dist/client/chat/message-components.js +37 -13
- package/dist/client/chat/tool-call-display.js +14 -0
- package/dist/client/chat-first/apps-rail.d.ts +1 -1
- package/dist/client/chat-first/apps-rail.js +6 -6
- package/dist/client/chat-first/copy.js +1 -0
- package/dist/client/chat-first/types.d.ts +1 -0
- package/dist/client/extensions/EmbeddedExtension.js +1 -1
- package/dist/client/extensions/ExtensionViewer.js +1 -1
- package/dist/client/extensions/InlineExtensionFrame.js +1 -1
- package/dist/client/onboarding/FirstRunOnboarding.js +17 -2
- package/dist/client/settings/FileStorageSettingsForm.d.ts +2 -0
- package/dist/client/settings/FileStorageSettingsForm.js +113 -0
- package/dist/client/settings/SettingsPanel.js +2 -1
- package/dist/client/sse-event-processor.js +21 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/extensions/actions.js +2 -2
- package/dist/extensions/html-shell.js +3 -1
- package/dist/file-upload/pre-upload-attachments.d.ts +15 -10
- package/dist/file-upload/pre-upload-attachments.js +68 -24
- package/dist/file-upload/registry.d.ts +4 -5
- package/dist/file-upload/registry.js +7 -14
- package/dist/file-upload/s3.d.ts +9 -0
- package/dist/file-upload/s3.js +168 -0
- package/dist/file-upload/types.d.ts +3 -3
- package/dist/file-upload/types.js +2 -2
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/notifications/routes.d.ts +6 -6
- package/dist/observability/routes.d.ts +3 -3
- package/dist/onboarding/app-profile.js +23 -1
- package/dist/onboarding/default-steps.js +69 -0
- package/dist/onboarding/types.d.ts +2 -0
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
- package/dist/provider-api/actions/provider-api.d.ts +8 -8
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/agent-chat/browser-team-tools.js +20 -0
- package/dist/server/agent-chat-plugin.js +12 -3
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth.js +45 -21
- package/dist/server/better-auth-instance.d.ts +2 -0
- package/dist/server/better-auth-instance.js +2 -2
- package/dist/server/core-routes-plugin.d.ts +1 -0
- package/dist/server/core-routes-plugin.js +26 -16
- package/dist/server/identity-sso-store.d.ts +3 -0
- package/dist/server/identity-sso-store.js +45 -0
- package/dist/server/identity-sso.d.ts +20 -10
- package/dist/server/identity-sso.js +79 -31
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/chat/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/dist/templates/default/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +24 -1
- package/dist/templates/workspace-core/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -2
- package/dist/templates/workspace-core/.agents/skills/extensions/references/api.md +1 -1
- package/dist/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +3 -1
- package/docs/content/authentication.mdx +27 -0
- package/docs/content/cross-app-sso.mdx +38 -8
- package/docs/content/extensions.mdx +15 -15
- package/docs/content/file-uploads.mdx +10 -12
- package/docs/content/generative-ui.mdx +6 -1
- package/package.json +2 -2
- package/src/templates/chat/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/src/templates/default/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +24 -1
- package/src/templates/workspace-core/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -2
- package/src/templates/workspace-core/.agents/skills/extensions/references/api.md +1 -1
- package/src/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +3 -1
package/corpus/templates/slides/app/components/editor/canvas-interactions/slides-canvas-adapter.ts
CHANGED
|
@@ -76,6 +76,17 @@ export function createSlidesCanvasInteractionCore(
|
|
|
76
76
|
return createCanvasInteractionCore(slidesCanvasInteractionConfig, adapter);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Slides only nudges selected layers for plain arrows and Shift+arrows. Other
|
|
81
|
+
* modifier chords stay native so browser and editing shortcuts keep working.
|
|
82
|
+
*/
|
|
83
|
+
export function resolveSlidesCanvasNudge(
|
|
84
|
+
input: Parameters<typeof slidesCanvasInteractionCore.nudge>[0],
|
|
85
|
+
) {
|
|
86
|
+
if (input.altKey || input.ctrlKey || input.metaKey) return null;
|
|
87
|
+
return slidesCanvasInteractionCore.nudge(input);
|
|
88
|
+
}
|
|
89
|
+
|
|
79
90
|
/** Creates one shared controller per live Slides pointer gesture. */
|
|
80
91
|
export function createSlidesCanvasGestureController(
|
|
81
92
|
adapter: SlidesCanvasGestureAdapter,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface InlineEditContentSnapshot {
|
|
2
|
+
slideId: string;
|
|
3
|
+
content: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function shouldPersistInlineEditContent(
|
|
7
|
+
initial: InlineEditContentSnapshot | null,
|
|
8
|
+
current: InlineEditContentSnapshot | null,
|
|
9
|
+
): boolean {
|
|
10
|
+
if (!current) return false;
|
|
11
|
+
return (
|
|
12
|
+
!initial ||
|
|
13
|
+
initial.slideId !== current.slideId ||
|
|
14
|
+
initial.content !== current.content
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -521,15 +521,31 @@ export function removeSlideObjectAndLayoutSpacer(element: HTMLElement): void {
|
|
|
521
521
|
element.remove();
|
|
522
522
|
}
|
|
523
523
|
|
|
524
|
+
/**
|
|
525
|
+
* Whether Delete may remove this selected element from the slide content.
|
|
526
|
+
*
|
|
527
|
+
* Selection is intentionally broader than freeform object manipulation: an
|
|
528
|
+
* AI-generated flow-layout div is still user content and must be removable.
|
|
529
|
+
* The renderer's structural shells and the hidden spacer used to preserve a
|
|
530
|
+
* moved object's original layout slot are not user content.
|
|
531
|
+
*/
|
|
532
|
+
export function isDeletableSlideElement(element: HTMLElement): boolean {
|
|
533
|
+
return (
|
|
534
|
+
!element.classList.contains("fmd-layout-spacer") &&
|
|
535
|
+
!element.classList.contains("fmd-slide") &&
|
|
536
|
+
!element.classList.contains("fmd-autofit-scale") &&
|
|
537
|
+
!element.hasAttribute("data-fmd-autofit-content") &&
|
|
538
|
+
!element.hasAttribute("data-slide-canvas")
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
|
|
524
542
|
/**
|
|
525
543
|
* Whether Delete should remove `element` even though it is not a freeform
|
|
526
|
-
* canvas object.
|
|
544
|
+
* canvas object or generic flow element.
|
|
527
545
|
*
|
|
528
|
-
*
|
|
529
|
-
*
|
|
530
|
-
*
|
|
531
|
-
* the surrounding grid or card intact. Without this, selecting a picture in a
|
|
532
|
-
* card grid and pressing Delete silently did nothing.
|
|
546
|
+
* Images are handled specially because they are leaves: an image nested in a
|
|
547
|
+
* card should be removed without swallowing the surrounding card. Generic
|
|
548
|
+
* flow elements are covered by `isDeletableSlideElement`.
|
|
533
549
|
*/
|
|
534
550
|
export function isDeletableFlowImage(element: HTMLElement): boolean {
|
|
535
551
|
return (
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** Shared style clipboard for copy/paste style (Cmd+Option+C / Cmd+Option+V) */
|
|
2
2
|
|
|
3
|
+
import type { SlideStylePatch, SlideStyleSnapshot } from "./slide-style";
|
|
4
|
+
|
|
3
5
|
export interface CopiedStyle {
|
|
4
6
|
color?: string;
|
|
5
7
|
bold?: boolean;
|
|
@@ -13,6 +15,65 @@ export function setCopiedStyle(s: CopiedStyle | null): void {
|
|
|
13
15
|
copiedStyle = s;
|
|
14
16
|
}
|
|
15
17
|
|
|
18
|
+
/** Appearance-only style payload used by the canvas editor. */
|
|
19
|
+
export type CopiedElementStyle = Pick<
|
|
20
|
+
SlideStylePatch,
|
|
21
|
+
| "color"
|
|
22
|
+
| "backgroundColor"
|
|
23
|
+
| "fontSize"
|
|
24
|
+
| "fontWeight"
|
|
25
|
+
| "fontStyle"
|
|
26
|
+
| "textDecoration"
|
|
27
|
+
| "lineHeight"
|
|
28
|
+
| "textAlign"
|
|
29
|
+
| "opacity"
|
|
30
|
+
| "borderRadius"
|
|
31
|
+
| "borderWidth"
|
|
32
|
+
| "borderColor"
|
|
33
|
+
| "paddingLeft"
|
|
34
|
+
| "paddingRight"
|
|
35
|
+
| "paddingTop"
|
|
36
|
+
| "paddingBottom"
|
|
37
|
+
>;
|
|
38
|
+
|
|
39
|
+
let copiedElementStyle: CopiedElementStyle | null = null;
|
|
40
|
+
|
|
41
|
+
function px(value: number): string {
|
|
42
|
+
return `${Number.isInteger(value) ? value : Number(value.toFixed(2))}px`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Converts the inspector's computed snapshot into an appearance-only patch. */
|
|
46
|
+
export function copiedElementStyleFromSnapshot(
|
|
47
|
+
snapshot: SlideStyleSnapshot,
|
|
48
|
+
): CopiedElementStyle {
|
|
49
|
+
return {
|
|
50
|
+
color: snapshot.color,
|
|
51
|
+
backgroundColor: snapshot.backgroundColor,
|
|
52
|
+
fontSize: px(snapshot.fontSize),
|
|
53
|
+
fontWeight: snapshot.fontWeight,
|
|
54
|
+
fontStyle: snapshot.fontStyle,
|
|
55
|
+
textDecoration: snapshot.textDecoration,
|
|
56
|
+
lineHeight: String(snapshot.lineHeight),
|
|
57
|
+
textAlign: snapshot.textAlign,
|
|
58
|
+
opacity: String(Math.max(0, Math.min(100, snapshot.opacity)) / 100),
|
|
59
|
+
borderRadius: px(snapshot.borderRadius),
|
|
60
|
+
borderWidth: px(snapshot.borderWidth),
|
|
61
|
+
borderColor: snapshot.borderColor,
|
|
62
|
+
paddingLeft: px(snapshot.paddingX),
|
|
63
|
+
paddingRight: px(snapshot.paddingX),
|
|
64
|
+
paddingTop: px(snapshot.paddingY),
|
|
65
|
+
paddingBottom: px(snapshot.paddingY),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function getCopiedElementStyle(): CopiedElementStyle | null {
|
|
70
|
+
return copiedElementStyle ? { ...copiedElementStyle } : null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function setCopiedElementStyle(style: CopiedElementStyle | null): void {
|
|
74
|
+
copiedElementStyle = style ? { ...style } : null;
|
|
75
|
+
}
|
|
76
|
+
|
|
16
77
|
// Brand palette — persisted in localStorage
|
|
17
78
|
const STORAGE_KEY = "slide-brand-palette";
|
|
18
79
|
|
|
@@ -9,6 +9,12 @@ import { useLocation } from "react-router";
|
|
|
9
9
|
|
|
10
10
|
import { useDecks } from "@/context/DeckContext";
|
|
11
11
|
import { useSidebarCollapsed } from "@/hooks/use-sidebar-collapsed";
|
|
12
|
+
import {
|
|
13
|
+
hasCurrentSlideSelection,
|
|
14
|
+
readPublishedSlidesSelection,
|
|
15
|
+
SLIDES_SELECTION_CHANGED_EVENT,
|
|
16
|
+
type SlidesAgentSelection,
|
|
17
|
+
} from "@/lib/slide-agent-context";
|
|
12
18
|
import { TAB_ID } from "@/lib/tab-id";
|
|
13
19
|
import { cn } from "@/lib/utils";
|
|
14
20
|
|
|
@@ -45,6 +51,8 @@ export function Layout({ children }: LayoutProps) {
|
|
|
45
51
|
const location = useLocation();
|
|
46
52
|
const t = useT();
|
|
47
53
|
const [sidebarOpen, setSidebarOpen] = useState(false);
|
|
54
|
+
const [slidesSelection, setSlidesSelection] =
|
|
55
|
+
useState<SlidesAgentSelection | null>(() => readPublishedSlidesSelection());
|
|
48
56
|
const [editorSidebarOverride, setEditorSidebarOverride] =
|
|
49
57
|
useState<EditorSidebarOverride | null>(null);
|
|
50
58
|
const { collapsed: sidebarCollapsed, setCollapsed: setSidebarCollapsed } =
|
|
@@ -53,6 +61,20 @@ export function Layout({ children }: LayoutProps) {
|
|
|
53
61
|
const isEmptyDecksState =
|
|
54
62
|
location.pathname === "/" && !decksLoading && decks.length === 0;
|
|
55
63
|
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
const onSelectionChanged = (event: Event) => {
|
|
66
|
+
setSlidesSelection(
|
|
67
|
+
(event as CustomEvent<SlidesAgentSelection | null>).detail ?? null,
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
window.addEventListener(SLIDES_SELECTION_CHANGED_EVENT, onSelectionChanged);
|
|
71
|
+
return () =>
|
|
72
|
+
window.removeEventListener(
|
|
73
|
+
SLIDES_SELECTION_CHANGED_EVENT,
|
|
74
|
+
onSelectionChanged,
|
|
75
|
+
);
|
|
76
|
+
}, []);
|
|
77
|
+
|
|
56
78
|
// Scope new chats to the deck the user is currently editing. The route
|
|
57
79
|
// is `/deck/:id`; everywhere else (list, presentation) leaves
|
|
58
80
|
// scope null so chats stay in the general pool. Keep the visible label
|
|
@@ -61,12 +83,14 @@ export function Layout({ children }: LayoutProps) {
|
|
|
61
83
|
const match = location.pathname.match(/^\/deck\/([^/]+)/);
|
|
62
84
|
const deckId = match?.[1];
|
|
63
85
|
if (!deckId) return null;
|
|
86
|
+
const hasSelection = hasCurrentSlideSelection(slidesSelection, deckId);
|
|
64
87
|
return {
|
|
65
88
|
type: "deck" as const,
|
|
66
89
|
id: deckId,
|
|
67
|
-
label: t("agent.
|
|
90
|
+
label: t(hasSelection ? "agent.currentSelection" : "agent.thisSlide"),
|
|
91
|
+
contextKey: "slides-current-context",
|
|
68
92
|
};
|
|
69
|
-
}, [location.pathname, t]);
|
|
93
|
+
}, [location.pathname, slidesSelection, t]);
|
|
70
94
|
|
|
71
95
|
useEffect(() => {
|
|
72
96
|
setSidebarOpen(false);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@agent-native/toolkit/ui/context-menu";
|
|
@@ -614,13 +614,12 @@ type PatchDeckFields = Extract<
|
|
|
614
614
|
export function applyOpToDeck(deck: Deck, op: PatchDeckOp): Deck {
|
|
615
615
|
switch (op.op) {
|
|
616
616
|
case "patch-slide": {
|
|
617
|
-
|
|
617
|
+
const prior = deck.slides.find((s) => s.id === op.slideId);
|
|
618
|
+
if (!prior || !hasChangedFields(prior, op.fields)) return deck;
|
|
618
619
|
const slides = deck.slides.map((s) => {
|
|
619
620
|
if (s.id !== op.slideId) return s;
|
|
620
|
-
changed = true;
|
|
621
621
|
return { ...s, ...op.fields };
|
|
622
622
|
});
|
|
623
|
-
if (!changed) return deck; // slide concurrently deleted — skip
|
|
624
623
|
return { ...deck, slides, updatedAt: new Date().toISOString() };
|
|
625
624
|
}
|
|
626
625
|
case "delete-slide": {
|
|
@@ -646,6 +645,12 @@ export function applyOpToDeck(deck: Deck, op: PatchDeckOp): Deck {
|
|
|
646
645
|
for (const s of deck.slides) {
|
|
647
646
|
if (!named.has(s.id)) reordered.push(s);
|
|
648
647
|
}
|
|
648
|
+
if (
|
|
649
|
+
reordered.length === deck.slides.length &&
|
|
650
|
+
reordered.every((slide, index) => slide.id === deck.slides[index]?.id)
|
|
651
|
+
) {
|
|
652
|
+
return deck;
|
|
653
|
+
}
|
|
649
654
|
return {
|
|
650
655
|
...deck,
|
|
651
656
|
slides: reordered,
|
|
@@ -672,6 +677,7 @@ export function applyOpToDeck(deck: Deck, op: PatchDeckOp): Deck {
|
|
|
672
677
|
return { ...deck, slides, updatedAt: new Date().toISOString() };
|
|
673
678
|
}
|
|
674
679
|
case "patch-deck-fields": {
|
|
680
|
+
if (!hasChangedFields(deck, op.fields)) return deck;
|
|
675
681
|
return {
|
|
676
682
|
...deck,
|
|
677
683
|
...op.fields,
|
|
@@ -681,6 +687,27 @@ export function applyOpToDeck(deck: Deck, op: PatchDeckOp): Deck {
|
|
|
681
687
|
}
|
|
682
688
|
}
|
|
683
689
|
|
|
690
|
+
function equalDeckValue(left: unknown, right: unknown): boolean {
|
|
691
|
+
if (Object.is(left, right)) return true;
|
|
692
|
+
if (
|
|
693
|
+
left === null ||
|
|
694
|
+
right === null ||
|
|
695
|
+
typeof left !== "object" ||
|
|
696
|
+
typeof right !== "object"
|
|
697
|
+
) {
|
|
698
|
+
return false;
|
|
699
|
+
}
|
|
700
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function hasChangedFields(current: object, fields: object): boolean {
|
|
704
|
+
const currentRecord = current as Record<string, unknown>;
|
|
705
|
+
const fieldRecord = fields as Record<string, unknown>;
|
|
706
|
+
return Object.keys(fieldRecord).some(
|
|
707
|
+
(key) => !equalDeckValue(currentRecord[key], fieldRecord[key]),
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
|
|
684
711
|
export function applyUndoOpToDecks(decks: Deck[], op: DeckUndoOp): Deck[] {
|
|
685
712
|
switch (op.op) {
|
|
686
713
|
case "delete-deck":
|
|
@@ -751,8 +778,11 @@ export function deriveInverseOp(
|
|
|
751
778
|
// Capture the prior value for every field this op touches, so undo
|
|
752
779
|
// restores exactly what changed (including clearing fields back to
|
|
753
780
|
// undefined).
|
|
754
|
-
(
|
|
781
|
+
if (!equalDeckValue(prior[key], op.fields[key])) {
|
|
782
|
+
(priorFields as Record<string, unknown>)[key] = prior[key];
|
|
783
|
+
}
|
|
755
784
|
}
|
|
785
|
+
if (Object.keys(priorFields).length === 0) return null;
|
|
756
786
|
return [{ op: "patch-slide", slideId: op.slideId, fields: priorFields }];
|
|
757
787
|
}
|
|
758
788
|
case "delete-slide": {
|
|
@@ -786,6 +816,7 @@ export function deriveInverseOp(
|
|
|
786
816
|
}
|
|
787
817
|
case "add-slide": {
|
|
788
818
|
// Inverse of adding a slide is deleting it.
|
|
819
|
+
if (before.slides.some((slide) => slide.id === op.slideId)) return null;
|
|
789
820
|
return [
|
|
790
821
|
{
|
|
791
822
|
op: "delete-slide",
|
|
@@ -796,6 +827,7 @@ export function deriveInverseOp(
|
|
|
796
827
|
}
|
|
797
828
|
case "reorder-slides": {
|
|
798
829
|
// Inverse reorder = the order the slides were in before.
|
|
830
|
+
if (applyOpToDeck(before, op) === before) return null;
|
|
799
831
|
return [
|
|
800
832
|
{ op: "reorder-slides", orderedIds: before.slides.map((s) => s.id) },
|
|
801
833
|
];
|
|
@@ -803,9 +835,13 @@ export function deriveInverseOp(
|
|
|
803
835
|
case "patch-deck-fields": {
|
|
804
836
|
const priorFields: Record<string, unknown> = {};
|
|
805
837
|
const beforeRecord = before as unknown as Record<string, unknown>;
|
|
838
|
+
const nextRecord = op.fields as Record<string, unknown>;
|
|
806
839
|
for (const key of Object.keys(op.fields)) {
|
|
807
|
-
|
|
840
|
+
if (!equalDeckValue(beforeRecord[key], nextRecord[key])) {
|
|
841
|
+
priorFields[key] = beforeRecord[key];
|
|
842
|
+
}
|
|
808
843
|
}
|
|
844
|
+
if (Object.keys(priorFields).length === 0) return null;
|
|
809
845
|
return [
|
|
810
846
|
{
|
|
811
847
|
op: "patch-deck-fields",
|
|
@@ -1734,7 +1770,8 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
1734
1770
|
target.tagName === "TEXTAREA" ||
|
|
1735
1771
|
target.tagName === "INPUT" ||
|
|
1736
1772
|
target.isContentEditable;
|
|
1737
|
-
|
|
1773
|
+
const key = e.key.toLowerCase();
|
|
1774
|
+
if ((e.metaKey || e.ctrlKey) && key === "z") {
|
|
1738
1775
|
if (isTyping) return;
|
|
1739
1776
|
e.preventDefault();
|
|
1740
1777
|
if (e.shiftKey) {
|
|
@@ -1743,7 +1780,7 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
1743
1780
|
undo();
|
|
1744
1781
|
}
|
|
1745
1782
|
}
|
|
1746
|
-
if ((e.metaKey || e.ctrlKey) &&
|
|
1783
|
+
if ((e.metaKey || e.ctrlKey) && key === "y") {
|
|
1747
1784
|
if (isTyping) return;
|
|
1748
1785
|
e.preventDefault();
|
|
1749
1786
|
redo();
|
|
@@ -1982,17 +2019,7 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
1982
2019
|
|
|
1983
2020
|
const updateDeck = useCallback(
|
|
1984
2021
|
(id: string, updates: Partial<Omit<Deck, "id" | "createdAt">>) => {
|
|
1985
|
-
// Clear the external-update suppression window so a rename/update that
|
|
1986
|
-
// happens within 2s of page load (or an SSE event) is not silently dropped.
|
|
1987
|
-
markDeckDirty(id);
|
|
1988
2022
|
const before = decksRef.current.find((d) => d.id === id);
|
|
1989
|
-
setDecks((prev) =>
|
|
1990
|
-
prev.map((d) =>
|
|
1991
|
-
d.id === id
|
|
1992
|
-
? { ...d, ...updates, updatedAt: new Date().toISOString() }
|
|
1993
|
-
: d,
|
|
1994
|
-
),
|
|
1995
|
-
);
|
|
1996
2023
|
// Enqueue a granular patch-deck-fields op — only the changed fields are
|
|
1997
2024
|
// sent to the server, so concurrent edits to slides are never clobbered.
|
|
1998
2025
|
// Exclude internal/derived fields that live only in client state.
|
|
@@ -2007,11 +2034,26 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
2007
2034
|
};
|
|
2008
2035
|
void _slides;
|
|
2009
2036
|
_ca;
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2037
|
+
const hasPersistableUpdates = Object.keys(persistableUpdates).length > 0;
|
|
2038
|
+
const op: PatchDeckOp | null = hasPersistableUpdates
|
|
2039
|
+
? {
|
|
2040
|
+
op: "patch-deck-fields",
|
|
2041
|
+
fields: persistableUpdates as PatchDeckFields,
|
|
2042
|
+
}
|
|
2043
|
+
: null;
|
|
2044
|
+
if (before && op && !deriveInverseOp(before, op)) return;
|
|
2045
|
+
|
|
2046
|
+
// Clear the external-update suppression window so a rename/update that
|
|
2047
|
+
// happens within 2s of page load (or an SSE event) is not silently dropped.
|
|
2048
|
+
markDeckDirty(id);
|
|
2049
|
+
setDecks((prev) =>
|
|
2050
|
+
prev.map((d) =>
|
|
2051
|
+
d.id === id
|
|
2052
|
+
? { ...d, ...updates, updatedAt: new Date().toISOString() }
|
|
2053
|
+
: d,
|
|
2054
|
+
),
|
|
2055
|
+
);
|
|
2056
|
+
if (op) {
|
|
2015
2057
|
enqueueDeckOp(id, op);
|
|
2016
2058
|
if (before) {
|
|
2017
2059
|
// Coalesce rapid deck-field edits (e.g. title typing, tweak sliders)
|
|
@@ -2087,7 +2129,6 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
2087
2129
|
updates: Partial<Omit<Slide, "id">>,
|
|
2088
2130
|
options?: UpdateSlideOptions,
|
|
2089
2131
|
) => {
|
|
2090
|
-
markDeckDirty(deckId);
|
|
2091
2132
|
const label = updates.layout
|
|
2092
2133
|
? "Change layout"
|
|
2093
2134
|
: updates.background
|
|
@@ -2096,6 +2137,9 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
2096
2137
|
? "Update content"
|
|
2097
2138
|
: "Edit slide";
|
|
2098
2139
|
const before = decksRef.current.find((d) => d.id === deckId);
|
|
2140
|
+
const op: PatchDeckOp = { op: "patch-slide", slideId, fields: updates };
|
|
2141
|
+
if (before && !deriveInverseOp(before, op)) return;
|
|
2142
|
+
markDeckDirty(deckId);
|
|
2099
2143
|
setDecksLocal((prev: Deck[]) =>
|
|
2100
2144
|
prev.map((d) => {
|
|
2101
2145
|
if (d.id !== deckId) return d;
|
|
@@ -2109,7 +2153,6 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
2109
2153
|
}),
|
|
2110
2154
|
);
|
|
2111
2155
|
// Granular op — only this slide's changed fields reach the server.
|
|
2112
|
-
const op: PatchDeckOp = { op: "patch-slide", slideId, fields: updates };
|
|
2113
2156
|
enqueueDeckOp(deckId, op, options);
|
|
2114
2157
|
if (before) {
|
|
2115
2158
|
// Coalesce a burst of edits to the SAME slide's SAME field-set into one
|
|
@@ -2223,11 +2266,18 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
2223
2266
|
|
|
2224
2267
|
const setDeckSlides = useCallback(
|
|
2225
2268
|
(deckId: string, slides: Slide[]) => {
|
|
2226
|
-
markDeckDirty(deckId);
|
|
2227
2269
|
const before = decksRef.current.find((deck) => deck.id === deckId);
|
|
2228
2270
|
const after = before
|
|
2229
2271
|
? { ...before, slides, updatedAt: new Date().toISOString() }
|
|
2230
2272
|
: null;
|
|
2273
|
+
if (
|
|
2274
|
+
before &&
|
|
2275
|
+
after &&
|
|
2276
|
+
deckContentSignature(before) === deckContentSignature(after)
|
|
2277
|
+
) {
|
|
2278
|
+
return;
|
|
2279
|
+
}
|
|
2280
|
+
markDeckDirty(deckId);
|
|
2231
2281
|
// setDeckSlides replaces ALL slides wholesale (used by AI generation and
|
|
2232
2282
|
// imports), so its undo entry is a deck-level full replacement instead of
|
|
2233
2283
|
// a fine-grained slide patch.
|
|
@@ -48,7 +48,8 @@ const messages = {
|
|
|
48
48
|
},
|
|
49
49
|
agent: {
|
|
50
50
|
emptyState: "Ask me anything about your presentations",
|
|
51
|
-
|
|
51
|
+
thisSlide: "This Slide",
|
|
52
|
+
currentSelection: "Current Selection",
|
|
52
53
|
suggestionPitch: "Build a 10-slide pitch from this doc",
|
|
53
54
|
suggestionBrand: "Apply our brand to this deck",
|
|
54
55
|
suggestionHero: "Generate a hero image for this slide",
|
|
@@ -436,6 +437,10 @@ const messages = {
|
|
|
436
437
|
order: "Order",
|
|
437
438
|
bringToFront: "Bring to front",
|
|
438
439
|
sendToBack: "Send to back",
|
|
440
|
+
copyStyle: "Copy style",
|
|
441
|
+
pasteStyle: "Paste style",
|
|
442
|
+
copy: "Copy",
|
|
443
|
+
paste: "Paste",
|
|
439
444
|
},
|
|
440
445
|
animations: {
|
|
441
446
|
appear: "Appear",
|
|
@@ -373,7 +373,7 @@ export async function startDeckGeneration({
|
|
|
373
373
|
: [
|
|
374
374
|
"This is a new deck. Keep it empty until generation begins; attached reference files must not seed it with imported slides.",
|
|
375
375
|
"Start a `manage-progress` run so progress appears in the app header. Add the first slide as soon as it is ready, then continue one slide at a time so the editor visibly fills in.",
|
|
376
|
-
`After reading any requested or attached reference material, but before adding the first slide, choose a concise, specific deck title from the user's request and source material. Never use the deck id, run id, file id, or another opaque alphanumeric token as the title. Call \`patch-deck\` with \`deckId: \"${deckId}\"\` and \`operations: [{ \"op\": \"patch-deck-fields\", \"fields\": { \"title\": \"<generated title>\" } }]\`. Include only \`title\` in \`fields\`; omit all other optional fields. Never leave a generated deck named \"Untitled Deck\" or another placeholder.`,
|
|
376
|
+
`After reading any requested or attached reference material, but before adding the first slide, choose a concise, specific deck title from the user's request and source material. Never use the deck id, run id, file id, uploaded filename, or another opaque alphanumeric token as the title. Do not reuse a generic placeholder like "Untitled scene" when the content or reference context gives you a better title. Call \`patch-deck\` with \`deckId: \"${deckId}\"\` and \`operations: [{ \"op\": \"patch-deck-fields\", \"fields\": { \"title\": \"<generated title>\" } }]\`. Include only \`title\` in \`fields\`; omit all other optional fields. Never leave a generated deck named \"Untitled Deck\" or another placeholder.`,
|
|
377
377
|
"If the user asks for a standalone visual, diagram, hero, one-pager, poster, or a couple of visuals, create only the requested one/few polished visual slides. Do not pad the result into a full presentation.",
|
|
378
378
|
"If the request is for a presentation or deck and does not explicitly ask for one slide, infer a coherent multi-slide outline from the scope and keep adding slides until that outline is complete. Do not stop after the first slide just because the prompt has few explicit instructions.",
|
|
379
379
|
`Add slides ONE AT A TIME using the \`add-slide\` action with --deckId=${deckId}. Wait for each \`add-slide\` result before calling it again; do not batch or parallelize slide writes.`,
|
|
@@ -20,11 +20,13 @@ export type ImageDropAgentPayload =
|
|
|
20
20
|
| {
|
|
21
21
|
kind: "hosted";
|
|
22
22
|
message: string;
|
|
23
|
+
context: string;
|
|
23
24
|
referenceImagePaths: string[];
|
|
24
25
|
}
|
|
25
26
|
| {
|
|
26
27
|
kind: "inline";
|
|
27
28
|
message: string;
|
|
29
|
+
context: string;
|
|
28
30
|
images: string[];
|
|
29
31
|
};
|
|
30
32
|
|
|
@@ -52,21 +54,18 @@ export function buildImageDropAgentPayload(args: {
|
|
|
52
54
|
args.intent.trim().length > 0
|
|
53
55
|
? args.intent.trim()
|
|
54
56
|
: "Use this image on the current slide.";
|
|
55
|
-
const
|
|
57
|
+
const contextLines: string[] = [];
|
|
56
58
|
if (args.contextHint && args.contextHint.trim().length > 0) {
|
|
57
|
-
|
|
59
|
+
contextLines.push(args.contextHint.trim());
|
|
58
60
|
}
|
|
59
|
-
|
|
61
|
+
contextLines.push(`Filename: ${args.filename}`);
|
|
60
62
|
|
|
61
63
|
if (args.upload.ok && args.upload.url) {
|
|
62
|
-
|
|
63
|
-
lines.length - 1,
|
|
64
|
-
0,
|
|
65
|
-
`Image URL (already uploaded): ${args.upload.url}`,
|
|
66
|
-
);
|
|
64
|
+
contextLines.push(`Image URL (already uploaded): ${args.upload.url}`);
|
|
67
65
|
return {
|
|
68
66
|
kind: "hosted",
|
|
69
|
-
message:
|
|
67
|
+
message: intentLine,
|
|
68
|
+
context: contextLines.join("\n\n"),
|
|
70
69
|
referenceImagePaths: [args.upload.url],
|
|
71
70
|
};
|
|
72
71
|
}
|
|
@@ -81,18 +80,19 @@ export function buildImageDropAgentPayload(args: {
|
|
|
81
80
|
if (!isMissingUploadProviderError(args.upload.status, args.upload.error)) {
|
|
82
81
|
// Unexpected upload failure — still try the inline path so the user can
|
|
83
82
|
// keep working, but tell the agent the hosted upload didn't land.
|
|
84
|
-
|
|
83
|
+
contextLines.push(
|
|
85
84
|
"Hosted upload failed; the image is attached inline as a data URL. Call upload-image on it before placing it on the slide if a durable URL is required.",
|
|
86
85
|
);
|
|
87
86
|
} else {
|
|
88
|
-
|
|
87
|
+
contextLines.push(
|
|
89
88
|
"No file upload provider is configured, so the image is attached inline as a data URL. Call upload-image to obtain a hosted URL before inserting it into slide HTML.",
|
|
90
89
|
);
|
|
91
90
|
}
|
|
92
91
|
|
|
93
92
|
return {
|
|
94
93
|
kind: "inline",
|
|
95
|
-
message:
|
|
94
|
+
message: intentLine,
|
|
95
|
+
context: contextLines.join("\n\n"),
|
|
96
96
|
images: [args.dataUrl],
|
|
97
97
|
};
|
|
98
98
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export const SLIDES_SELECTION_CHANGED_EVENT = "slides:selection-changed";
|
|
2
|
+
|
|
3
|
+
export interface SlidesAgentSelection {
|
|
4
|
+
deckId?: string;
|
|
5
|
+
slideId?: string;
|
|
6
|
+
slideIndex?: number;
|
|
7
|
+
slideNumber?: number;
|
|
8
|
+
items?: readonly unknown[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface SlidesSelectionWindow extends Window {
|
|
12
|
+
__slidesAgentSelection?: SlidesAgentSelection | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getSlidesWindow(): SlidesSelectionWindow | null {
|
|
16
|
+
return typeof window === "undefined"
|
|
17
|
+
? null
|
|
18
|
+
: (window as SlidesSelectionWindow);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Keep the visible scope chip responsive without making the browser event the
|
|
23
|
+
* source of truth for agent context. The app-state write remains canonical.
|
|
24
|
+
*/
|
|
25
|
+
export function publishSlidesSelection(
|
|
26
|
+
selection: SlidesAgentSelection | null,
|
|
27
|
+
): void {
|
|
28
|
+
const slidesWindow = getSlidesWindow();
|
|
29
|
+
if (!slidesWindow) return;
|
|
30
|
+
slidesWindow.__slidesAgentSelection = selection;
|
|
31
|
+
slidesWindow.dispatchEvent(
|
|
32
|
+
new CustomEvent<SlidesAgentSelection | null>(
|
|
33
|
+
SLIDES_SELECTION_CHANGED_EVENT,
|
|
34
|
+
{ detail: selection },
|
|
35
|
+
),
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function readPublishedSlidesSelection(): SlidesAgentSelection | null {
|
|
40
|
+
return getSlidesWindow()?.__slidesAgentSelection ?? null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function hasCurrentSlideSelection(
|
|
44
|
+
selection: SlidesAgentSelection | null,
|
|
45
|
+
deckId: string,
|
|
46
|
+
): boolean {
|
|
47
|
+
return (
|
|
48
|
+
selection?.deckId === deckId &&
|
|
49
|
+
Array.isArray(selection.items) &&
|
|
50
|
+
selection.items.length > 0
|
|
51
|
+
);
|
|
52
|
+
}
|