@agent-native/core 0.85.7 → 0.86.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 +2 -2
- package/corpus/core/CHANGELOG.md +124 -1
- package/corpus/core/README.md +0 -11
- package/corpus/core/docs/content/cloneable-saas.mdx +0 -1
- package/corpus/core/docs/content/locales/ar-SA/cloneable-saas.mdx +0 -1
- package/corpus/core/docs/content/locales/ar-SA/plan-plugin.mdx +10 -9
- package/corpus/core/docs/content/locales/ar-SA/sharing.mdx +1 -2
- package/corpus/core/docs/content/locales/ar-SA/skills-guide.mdx +2 -3
- package/corpus/core/docs/content/locales/ar-SA/template-plan.mdx +23 -7
- package/corpus/core/docs/content/locales/ar-SA/tracking.mdx +1 -1
- package/corpus/core/docs/content/locales/ar-SA/workspace-connections.mdx +11 -11
- package/corpus/core/docs/content/locales/de-DE/cloneable-saas.mdx +0 -1
- package/corpus/core/docs/content/locales/de-DE/plan-plugin.mdx +10 -9
- package/corpus/core/docs/content/locales/de-DE/sharing.mdx +1 -2
- package/corpus/core/docs/content/locales/de-DE/skills-guide.mdx +2 -3
- package/corpus/core/docs/content/locales/de-DE/template-plan.mdx +23 -7
- package/corpus/core/docs/content/locales/de-DE/tracking.mdx +1 -1
- package/corpus/core/docs/content/locales/de-DE/workspace-connections.mdx +1 -1
- package/corpus/core/docs/content/locales/es-ES/cloneable-saas.mdx +0 -1
- package/corpus/core/docs/content/locales/es-ES/plan-plugin.mdx +10 -9
- package/corpus/core/docs/content/locales/es-ES/sharing.mdx +1 -2
- package/corpus/core/docs/content/locales/es-ES/skills-guide.mdx +2 -3
- package/corpus/core/docs/content/locales/es-ES/template-plan.mdx +23 -7
- package/corpus/core/docs/content/locales/es-ES/tracking.mdx +1 -1
- package/corpus/core/docs/content/locales/es-ES/workspace-connections.mdx +1 -1
- package/corpus/core/docs/content/locales/fr-FR/cloneable-saas.mdx +0 -1
- package/corpus/core/docs/content/locales/fr-FR/plan-plugin.mdx +10 -9
- package/corpus/core/docs/content/locales/fr-FR/sharing.mdx +1 -2
- package/corpus/core/docs/content/locales/fr-FR/skills-guide.mdx +2 -3
- package/corpus/core/docs/content/locales/fr-FR/template-plan.mdx +23 -7
- package/corpus/core/docs/content/locales/fr-FR/tracking.mdx +1 -1
- package/corpus/core/docs/content/locales/fr-FR/workspace-connections.mdx +1 -1
- package/corpus/core/docs/content/locales/hi-IN/cloneable-saas.mdx +0 -1
- package/corpus/core/docs/content/locales/hi-IN/plan-plugin.mdx +10 -9
- package/corpus/core/docs/content/locales/hi-IN/sharing.mdx +1 -2
- package/corpus/core/docs/content/locales/hi-IN/skills-guide.mdx +2 -3
- package/corpus/core/docs/content/locales/hi-IN/template-plan.mdx +23 -7
- package/corpus/core/docs/content/locales/hi-IN/tracking.mdx +1 -1
- package/corpus/core/docs/content/locales/hi-IN/workspace-connections.mdx +1 -1
- package/corpus/core/docs/content/locales/ja-JP/cloneable-saas.mdx +0 -1
- package/corpus/core/docs/content/locales/ja-JP/plan-plugin.mdx +10 -9
- package/corpus/core/docs/content/locales/ja-JP/sharing.mdx +1 -2
- package/corpus/core/docs/content/locales/ja-JP/skills-guide.mdx +2 -3
- package/corpus/core/docs/content/locales/ja-JP/template-plan.mdx +23 -7
- package/corpus/core/docs/content/locales/ja-JP/tracking.mdx +1 -1
- package/corpus/core/docs/content/locales/ja-JP/workspace-connections.mdx +1 -1
- package/corpus/core/docs/content/locales/ko-KR/cloneable-saas.mdx +0 -1
- package/corpus/core/docs/content/locales/ko-KR/plan-plugin.mdx +10 -9
- package/corpus/core/docs/content/locales/ko-KR/sharing.mdx +1 -2
- package/corpus/core/docs/content/locales/ko-KR/skills-guide.mdx +2 -3
- package/corpus/core/docs/content/locales/ko-KR/template-plan.mdx +23 -7
- package/corpus/core/docs/content/locales/ko-KR/tracking.mdx +1 -1
- package/corpus/core/docs/content/locales/ko-KR/workspace-connections.mdx +1 -1
- package/corpus/core/docs/content/locales/pt-BR/cloneable-saas.mdx +0 -1
- package/corpus/core/docs/content/locales/pt-BR/plan-plugin.mdx +10 -9
- package/corpus/core/docs/content/locales/pt-BR/sharing.mdx +1 -2
- package/corpus/core/docs/content/locales/pt-BR/skills-guide.mdx +2 -3
- package/corpus/core/docs/content/locales/pt-BR/template-plan.mdx +23 -7
- package/corpus/core/docs/content/locales/pt-BR/tracking.mdx +1 -1
- package/corpus/core/docs/content/locales/pt-BR/workspace-connections.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-CN/cloneable-saas.mdx +0 -1
- package/corpus/core/docs/content/locales/zh-CN/plan-plugin.mdx +10 -9
- package/corpus/core/docs/content/locales/zh-CN/sharing.mdx +1 -2
- package/corpus/core/docs/content/locales/zh-CN/skills-guide.mdx +2 -3
- package/corpus/core/docs/content/locales/zh-CN/template-plan.mdx +23 -7
- package/corpus/core/docs/content/locales/zh-CN/tracking.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-CN/workspace-connections.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-TW/cloneable-saas.mdx +0 -1
- package/corpus/core/docs/content/locales/zh-TW/plan-plugin.mdx +10 -9
- package/corpus/core/docs/content/locales/zh-TW/sharing.mdx +1 -2
- package/corpus/core/docs/content/locales/zh-TW/skills-guide.mdx +2 -3
- package/corpus/core/docs/content/locales/zh-TW/template-plan.mdx +23 -7
- package/corpus/core/docs/content/locales/zh-TW/tracking.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-TW/workspace-connections.mdx +1 -1
- package/corpus/core/docs/content/plan-plugin.mdx +12 -11
- package/corpus/core/docs/content/sharing.mdx +1 -2
- package/corpus/core/docs/content/skills-guide.mdx +4 -3
- package/corpus/core/docs/content/template-plan.mdx +37 -6
- package/corpus/core/docs/content/tracking.mdx +1 -1
- package/corpus/core/docs/content/workspace-connections.mdx +1 -1
- package/corpus/core/package.json +6 -6
- package/corpus/core/src/agent/run-store.ts +1 -1
- package/corpus/core/src/chat-threads/store.ts +73 -11
- package/corpus/core/src/checkpoints/store.ts +13 -2
- package/corpus/core/src/cli/create.ts +3 -4
- package/corpus/core/src/cli/design-connect.ts +469 -9
- package/corpus/core/src/cli/index.ts +13 -0
- package/corpus/core/src/cli/plan-local.ts +281 -24
- package/corpus/core/src/cli/skills.ts +177 -21
- package/corpus/core/src/cli/templates-meta.ts +0 -13
- package/corpus/core/src/cli/visualize-repo.ts +715 -0
- package/corpus/core/src/client/AssistantChat.tsx +126 -17
- package/corpus/core/src/client/agent-chat-adapter.ts +6 -5
- package/corpus/core/src/client/app-providers.tsx +2 -2
- package/corpus/core/src/client/collab/index.ts +31 -0
- package/corpus/core/src/client/components/LiveCursorOverlay.tsx +16 -1
- package/corpus/core/src/client/components/PresenceBar.tsx +14 -1
- package/corpus/core/src/client/components/RecentEditHighlights.tsx +219 -0
- package/corpus/core/src/client/components/RemoteSelectionRings.tsx +40 -6
- package/corpus/core/src/client/index.ts +48 -0
- package/corpus/core/src/client/org/OrgSwitcher.tsx +0 -4
- package/corpus/core/src/client/rich-markdown-editor/SharedRichEditor.tsx +49 -32
- package/corpus/core/src/client/rich-markdown-editor/index.ts +6 -0
- package/corpus/core/src/client/rich-markdown-editor/surgical-apply.ts +192 -0
- package/corpus/core/src/client/rich-markdown-editor/useCollabReconcile.ts +59 -8
- package/corpus/core/src/client/session-replay.ts +181 -15
- package/corpus/core/src/client/sse-event-processor.ts +90 -1
- package/corpus/core/src/client/use-action.ts +123 -25
- package/corpus/core/src/client/use-agent-engine-configured.ts +7 -1
- package/corpus/core/src/client/use-chat-threads.ts +18 -7
- package/corpus/core/src/client/use-db-sync.ts +104 -19
- package/corpus/core/src/client/visual-style-controls.tsx +475 -0
- package/corpus/core/src/collab/agent-presence.ts +191 -62
- package/corpus/core/src/collab/awareness.ts +83 -6
- package/corpus/core/src/collab/client.ts +89 -146
- package/corpus/core/src/collab/index.ts +34 -0
- package/corpus/core/src/collab/recent-edits.ts +171 -0
- package/corpus/core/src/collab/undo.ts +432 -0
- package/corpus/core/src/collab/ydoc-manager.ts +68 -0
- package/corpus/core/src/connections/catalog.ts +2 -3
- package/corpus/core/src/db/create-get-db.ts +86 -1
- package/corpus/core/src/db/ensure-additive-columns.ts +430 -0
- package/corpus/core/src/db/index.ts +6 -0
- package/corpus/core/src/db/migrations.ts +236 -35
- package/corpus/core/src/jobs/scheduler.ts +17 -14
- package/corpus/core/src/observability/traces.ts +182 -1
- package/corpus/core/src/org/context.ts +79 -2
- package/corpus/core/src/server/agent-chat-plugin.ts +10 -6
- package/corpus/core/src/server/auth-marketing.ts +0 -11
- package/corpus/core/src/server/auth.ts +11 -4
- package/corpus/core/src/server/collab-plugin.ts +56 -13
- package/corpus/core/src/server/design-token-utils.ts +1 -1
- package/corpus/core/src/server/fusion-app.ts +447 -0
- package/corpus/core/src/server/index.ts +13 -0
- package/corpus/core/src/server/onboarding-html.ts +3 -4
- package/corpus/core/src/server/poll-events.ts +14 -2
- package/corpus/core/src/server/poll.ts +219 -12
- package/corpus/core/src/server/schema-prompt.ts +28 -9
- package/corpus/core/src/settings/store.ts +33 -2
- package/corpus/core/src/sharing/actions/set-resource-visibility.ts +2 -0
- package/corpus/core/src/sharing/actions/share-resource.ts +3 -0
- package/corpus/core/src/sharing/actions/unshare-resource.ts +2 -0
- package/corpus/core/src/styles/agent-native.css +0 -5
- package/corpus/core/src/templates/default/.agents/skills/real-time-collab/SKILL.md +82 -19
- package/corpus/core/src/templates/default/.agents/skills/real-time-sync/SKILL.md +15 -0
- package/corpus/core/src/templates/default/.agents/skills/storing-data/SKILL.md +8 -0
- package/corpus/core/src/templates/default/package.json +3 -3
- package/corpus/core/src/templates/workspace-core/.agents/skills/client-methods/SKILL.md +8 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +22 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/real-time-collab/SKILL.md +82 -19
- package/corpus/core/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +15 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/storing-data/SKILL.md +8 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -0
- package/corpus/core/src/tracking/providers.ts +22 -1
- package/corpus/templates/analytics/.agents/skills/real-time-sync/SKILL.md +15 -0
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +20 -5
- package/corpus/templates/analytics/.agents/skills/storing-data/SKILL.md +8 -0
- package/corpus/templates/analytics/AGENTS.md +33 -6
- package/corpus/templates/analytics/actions/navigate.ts +14 -4
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +1 -1
- package/corpus/templates/analytics/actions/view-screen.ts +57 -0
- package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +23 -1
- package/corpus/templates/analytics/app/components/layout/Header.tsx +1 -0
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +21 -0
- package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +7 -0
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +15 -1
- package/corpus/templates/analytics/app/i18n-data.ts +151 -10
- package/corpus/templates/analytics/app/pages/Agents.tsx +118 -0
- package/corpus/templates/analytics/app/pages/DataSources.tsx +11 -1
- package/corpus/templates/analytics/app/pages/sessions/SessionDevToolsPanel.tsx +76 -40
- package/corpus/templates/analytics/app/pages/sessions/session-replay-devtools.ts +5 -0
- package/corpus/templates/analytics/app/routes/agents.tsx +10 -0
- package/corpus/templates/analytics/changelog/2026-07-03-agent-admin-tools-page.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-03-agent-llm-observability-dashboard.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-03-session-recordings-now-include-response-bodies-for-5xx-error.md +6 -0
- package/corpus/templates/analytics/docs/schemas/first-party-analytics.md +34 -0
- package/corpus/templates/analytics/docs/schemas/tracked-events.md +6 -0
- package/corpus/templates/analytics/package.json +3 -3
- package/corpus/templates/analytics/seeds/dashboards/agent-observability-llm.json +173 -0
- package/corpus/templates/analytics/server/jobs/analytics-alerts.ts +23 -5
- package/corpus/templates/analytics/server/lib/analytics-alerts.ts +58 -13
- package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +23 -0
- package/corpus/templates/analytics/server/lib/dashboard-seeds.ts +5 -0
- package/corpus/templates/analytics/server/lib/session-replay-agent-context.ts +120 -8
- package/corpus/templates/analytics/server/lib/session-replay.ts +3 -2
- package/corpus/templates/analytics/server/plugins/db.ts +93 -2
- package/corpus/templates/analytics/server/routes/api/session-replay/agent-diagnostics.json.get.ts +45 -1
- package/corpus/templates/analytics/shared/session-replay-diagnostics.ts +10 -0
- package/corpus/templates/assets/.agents/skills/real-time-collab/SKILL.md +82 -19
- package/corpus/templates/assets/.agents/skills/real-time-sync/SKILL.md +15 -0
- package/corpus/templates/assets/.agents/skills/storing-data/SKILL.md +8 -0
- package/corpus/templates/assets/package.json +3 -3
- package/corpus/templates/assets/server/plugins/db.ts +63 -2
- package/corpus/templates/brain/.agents/skills/real-time-sync/SKILL.md +15 -0
- package/corpus/templates/brain/package.json +3 -3
- package/corpus/templates/brain/server/plugins/db.ts +75 -2
- package/corpus/templates/calendar/.agents/skills/real-time-sync/SKILL.md +15 -0
- package/corpus/templates/calendar/.agents/skills/storing-data/SKILL.md +8 -0
- package/corpus/templates/calendar/package.json +3 -3
- package/corpus/templates/calendar/server/plugins/db.ts +64 -2
- package/corpus/templates/chat/.agents/skills/real-time-sync/SKILL.md +15 -0
- package/corpus/templates/chat/package.json +3 -3
- package/corpus/templates/clips/actions/finalize-recording.ts +50 -40
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +8 -6
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +3 -3
- package/corpus/templates/clips/changelog/2026-07-03-mobile-recording-pages-keep-videos-in-a-16-9-player-with-act.md +6 -0
- package/corpus/templates/clips/package.json +3 -3
- package/corpus/templates/clips/server/lib/recording-upload-state.ts +56 -0
- package/corpus/templates/clips/server/plugins/db.ts +68 -1
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +27 -3
- package/corpus/templates/content/.agents/skills/real-time-sync/SKILL.md +15 -0
- package/corpus/templates/content/.agents/skills/storing-data/SKILL.md +8 -0
- package/corpus/templates/content/actions/edit-document.ts +48 -1
- package/corpus/templates/content/actions/list-documents.ts +101 -94
- package/corpus/templates/content/actions/search-documents.ts +19 -3
- package/corpus/templates/content/actions/update-document.ts +54 -1
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +127 -7
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +97 -2
- package/corpus/templates/content/changelog/2026-07-03-agent-document-edits-now-appear-as-a-live-collaborator-prese.md +6 -0
- package/corpus/templates/content/changelog/2026-07-03-document-search-and-list-views-load-faster-on-large-workspac.md +6 -0
- package/corpus/templates/content/package.json +3 -3
- package/corpus/templates/content/server/plugins/db.ts +57 -1
- package/corpus/templates/design/.agents/skills/full-app-build/SKILL.md +140 -0
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +20 -0
- package/corpus/templates/design/AGENTS.md +50 -12
- package/corpus/templates/design/actions/add-fusion-screens.ts +91 -0
- package/corpus/templates/design/actions/apply-a11y-fix.ts +5 -1
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +10 -2
- package/corpus/templates/design/actions/apply-fusion-edits.ts +161 -0
- package/corpus/templates/design/actions/apply-visual-edit.ts +34 -1
- package/corpus/templates/design/actions/create-component.ts +5 -1
- package/corpus/templates/design/actions/create-fusion-app.ts +202 -0
- package/corpus/templates/design/actions/deploy-fusion-app.ts +123 -0
- package/corpus/templates/design/actions/edit-design.ts +13 -16
- package/corpus/templates/design/actions/get-fusion-deploy-status.ts +86 -0
- package/corpus/templates/design/actions/list-fusion-edits.ts +96 -0
- package/corpus/templates/design/actions/list-local-files.ts +133 -0
- package/corpus/templates/design/actions/push-fusion-app.ts +62 -0
- package/corpus/templates/design/actions/queue-fusion-edit.ts +112 -0
- package/corpus/templates/design/actions/read-local-file.ts +139 -0
- package/corpus/templates/design/actions/send-fusion-message.ts +69 -0
- package/corpus/templates/design/actions/sync-fusion-app.ts +196 -0
- package/corpus/templates/design/actions/write-local-file.ts +115 -14
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +12 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +379 -0
- package/corpus/templates/design/app/components/design/EditPanel.tsx +149 -73
- package/corpus/templates/design/app/components/design/FusionAppBanner.tsx +328 -0
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +148 -30
- package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +1 -1
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +567 -6
- package/corpus/templates/design/app/components/design/TweaksPanel.tsx +7 -98
- package/corpus/templates/design/app/components/design/code-workbench/ActivityBar.tsx +85 -0
- package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbench.tsx +466 -0
- package/corpus/templates/design/app/components/design/code-workbench/SideBar.tsx +53 -0
- package/corpus/templates/design/app/components/design/code-workbench/commands.ts +339 -0
- package/corpus/templates/design/app/components/design/code-workbench/editor/Breadcrumbs.tsx +42 -0
- package/corpus/templates/design/app/components/design/code-workbench/editor/BufferSync.tsx +128 -0
- package/corpus/templates/design/app/components/design/code-workbench/editor/EditorTabs.tsx +363 -0
- package/corpus/templates/design/app/components/design/code-workbench/editor/MonacoHost.tsx +268 -0
- package/corpus/templates/design/app/components/design/code-workbench/editor/StatusBar.tsx +208 -0
- package/corpus/templates/design/app/components/design/code-workbench/editor/monaco-setup.ts +61 -0
- package/corpus/templates/design/app/components/design/code-workbench/editor/monaco-theme-palette.ts +191 -0
- package/corpus/templates/design/app/components/design/code-workbench/editor/monaco-theme.ts +29 -0
- package/corpus/templates/design/app/components/design/code-workbench/editor/status-bar-lang.ts +21 -0
- package/corpus/templates/design/app/components/design/code-workbench/editor/tab-drag.ts +27 -0
- package/corpus/templates/design/app/components/design/code-workbench/explorer/ExplorerView.tsx +155 -0
- package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +551 -0
- package/corpus/templates/design/app/components/design/code-workbench/explorer/file-icons.tsx +115 -0
- package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open.ts +65 -0
- package/corpus/templates/design/app/components/design/code-workbench/format/prettier-format.ts +182 -0
- package/corpus/templates/design/app/components/design/code-workbench/model-registry.ts +133 -0
- package/corpus/templates/design/app/components/design/code-workbench/quickinput/QuickInput.tsx +563 -0
- package/corpus/templates/design/app/components/design/code-workbench/quickinput/fuzzy.ts +170 -0
- package/corpus/templates/design/app/components/design/code-workbench/search/SearchView.tsx +508 -0
- package/corpus/templates/design/app/components/design/code-workbench/search/search-engine.ts +280 -0
- package/corpus/templates/design/app/components/design/code-workbench/store.tsx +734 -0
- package/corpus/templates/design/app/components/design/code-workbench/workspace/create-providers.ts +39 -0
- package/corpus/templates/design/app/components/design/code-workbench/workspace/inline-provider.ts +196 -0
- package/corpus/templates/design/app/components/design/code-workbench/workspace/localhost-provider.ts +177 -0
- package/corpus/templates/design/app/components/design/code-workbench/workspace/tree.ts +110 -0
- package/corpus/templates/design/app/components/design/code-workbench/workspace/types.ts +103 -0
- package/corpus/templates/design/app/components/design/code-workbench-theme.ts +68 -0
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +127 -10
- package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +52 -29
- package/corpus/templates/design/app/components/design/inspector/index.ts +9 -0
- package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +56 -0
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +80 -1
- package/corpus/templates/design/app/components/visual-editor/SaveStatusIndicator.tsx +6 -16
- package/corpus/templates/design/app/i18n/zh-TW.ts +2 -1
- package/corpus/templates/design/app/i18n-data.ts +31 -18
- package/corpus/templates/design/app/pages/DesignEditor.tsx +1474 -175
- package/corpus/templates/design/app/pages/Index.tsx +90 -17
- package/corpus/templates/design/changelog/2026-07-03-the-ai-now-edits-like-a-collaborator-on-canvas-its-cursor-gl.md +6 -0
- package/corpus/templates/design/changelog/2026-07-03-the-code-panel-is-now-a-full-vs-code-style-editor-with-a-fil.md +6 -0
- package/corpus/templates/design/package.json +4 -3
- package/corpus/templates/design/server/db/schema.ts +31 -0
- package/corpus/templates/design/server/lib/fusion-screens.ts +246 -0
- package/corpus/templates/design/server/plugins/db.ts +89 -2
- package/corpus/templates/design/shared/collab-selection.ts +52 -0
- package/corpus/templates/design/shared/full-app.ts +126 -0
- package/corpus/templates/design/shared/pen-path.ts +296 -0
- package/corpus/templates/dispatch/package.json +3 -3
- package/corpus/templates/forms/.agents/skills/real-time-sync/SKILL.md +15 -0
- package/corpus/templates/forms/.agents/skills/storing-data/SKILL.md +8 -0
- package/corpus/templates/forms/package.json +3 -3
- package/corpus/templates/forms/server/plugins/db.ts +62 -2
- package/corpus/templates/macros/package.json +3 -3
- package/corpus/templates/macros/server/plugins/db.ts +63 -2
- package/corpus/templates/mail/.agents/skills/real-time-sync/SKILL.md +15 -0
- package/corpus/templates/mail/.agents/skills/storing-data/SKILL.md +8 -0
- package/corpus/templates/mail/package.json +3 -3
- package/corpus/templates/mail/server/lib/contact-frequency.ts +42 -15
- package/corpus/templates/mail/server/plugins/db.ts +63 -2
- package/corpus/templates/plan/.agents/skills/real-time-sync/SKILL.md +15 -0
- package/corpus/templates/plan/.agents/skills/visual-plan/SKILL.md +9 -3
- package/corpus/templates/plan/.agents/skills/visualize-repo/SKILL.md +92 -0
- package/corpus/templates/plan/actions/export-visual-plan.ts +5 -0
- package/corpus/templates/plan/actions/get-local-plan-folder.ts +4 -22
- package/corpus/templates/plan/actions/get-plan-version.ts +6 -16
- package/corpus/templates/plan/actions/list-plan-versions.ts +48 -20
- package/corpus/templates/plan/actions/patch-visual-plan-source.ts +41 -1
- package/corpus/templates/plan/actions/promote-local-plan-folder.ts +4 -22
- package/corpus/templates/plan/actions/update-local-plan-comments.ts +4 -2
- package/corpus/templates/plan/actions/update-local-plan-folder.ts +4 -22
- package/corpus/templates/plan/actions/update-visual-plan.ts +60 -1
- package/corpus/templates/plan/agent-native.app-skill.json +10 -0
- package/corpus/templates/plan/app/components/editor/PlanDocumentEditor.tsx +138 -39
- package/corpus/templates/plan/app/components/editor/usePlanUndoStack.ts +165 -23
- package/corpus/templates/plan/app/components/plan/PlanContentRenderer.tsx +80 -4
- package/corpus/templates/plan/app/global.css +11 -9
- package/corpus/templates/plan/app/hooks/use-navigation-state.ts +10 -1
- package/corpus/templates/plan/app/hooks/use-plan-presence.ts +66 -0
- package/corpus/templates/plan/app/pages/PlansPage.tsx +215 -63
- package/corpus/templates/plan/changelog/2026-07-03-local-visual-plans-and-recaps-can-save-comments-without-sign.md +6 -0
- package/corpus/templates/plan/changelog/2026-07-03-plan-tabs-render-nested-text-instead-of-raw-json.md +6 -0
- package/corpus/templates/plan/changelog/2026-07-03-plans-are-now-fully-real-time-collaborative-multiple-people-.md +6 -0
- package/corpus/templates/plan/changelog/2026-07-03-plans-open-and-refresh-faster-especially-long-lived-plans-wi.md +6 -0
- package/corpus/templates/plan/changelog/2026-07-03-undo-now-survives-agent-edits-cmd-z-keeps-working-on-your-ow.md +6 -0
- package/corpus/templates/plan/package.json +5 -3
- package/corpus/templates/plan/server/db/schema.ts +12 -0
- package/corpus/templates/plan/server/lib/local-plan-kind.ts +25 -0
- package/corpus/templates/plan/server/lib/plan-versions.ts +83 -1
- package/corpus/templates/plan/server/lib/public-action-paths.ts +0 -2
- package/corpus/templates/plan/server/plans.ts +26 -3
- package/corpus/templates/plan/server/plugins/db.ts +105 -2
- package/corpus/templates/slides/.agents/skills/slide-editing/SKILL.md +8 -4
- package/corpus/templates/slides/AGENTS.md +4 -0
- package/corpus/templates/slides/actions/_agent-presence.ts +48 -0
- package/corpus/templates/slides/actions/add-slide.ts +16 -18
- package/corpus/templates/slides/actions/patch-deck.ts +4 -2
- package/corpus/templates/slides/actions/update-slide.ts +114 -77
- package/corpus/templates/slides/actions/view-screen.ts +39 -0
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +47 -3
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +32 -35
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +625 -199
- package/corpus/templates/slides/app/components/editor/SlideStyleInspector.tsx +342 -0
- package/corpus/templates/slides/app/components/editor/TweaksPanel.tsx +46 -96
- package/corpus/templates/slides/app/components/visual-editor/SaveStatusIndicator.tsx +8 -1
- package/corpus/templates/slides/app/context/DeckContext.tsx +589 -176
- package/corpus/templates/slides/app/hooks/use-deck-presence.ts +84 -42
- package/corpus/templates/slides/app/i18n/ar-SA.ts +46 -0
- package/corpus/templates/slides/app/i18n/de-DE.ts +46 -0
- package/corpus/templates/slides/app/i18n/en-US.ts +46 -0
- package/corpus/templates/slides/app/i18n/es-ES.ts +46 -0
- package/corpus/templates/slides/app/i18n/fr-FR.ts +46 -0
- package/corpus/templates/slides/app/i18n/hi-IN.ts +46 -0
- package/corpus/templates/slides/app/i18n/ja-JP.ts +46 -0
- package/corpus/templates/slides/app/i18n/ko-KR.ts +46 -0
- package/corpus/templates/slides/app/i18n/pt-BR.ts +46 -0
- package/corpus/templates/slides/app/i18n/zh-CN.ts +46 -0
- package/corpus/templates/slides/app/i18n/zh-TW.ts +46 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +25 -13
- package/corpus/templates/slides/changelog/2026-07-03-figma-style-slide-editing.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-03-see-the-ai-working-with-you-when-the-agent-edits-a-slide-it-.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-03-undo-redo-is-now-precise-and-safe-with-collaborators-it-only.md +6 -0
- package/corpus/templates/slides/package.json +3 -3
- package/corpus/templates/slides/server/handlers/decks.ts +31 -3
- package/corpus/templates/slides/server/plugins/db.ts +65 -2
- package/corpus/templates/videos/netlify.toml +1 -12
- package/dist/agent/run-store.js +1 -1
- package/dist/agent/run-store.js.map +1 -1
- package/dist/chat-threads/store.d.ts +9 -1
- package/dist/chat-threads/store.d.ts.map +1 -1
- package/dist/chat-threads/store.js +63 -11
- package/dist/chat-threads/store.js.map +1 -1
- package/dist/checkpoints/store.d.ts.map +1 -1
- package/dist/checkpoints/store.js +10 -2
- package/dist/checkpoints/store.js.map +1 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +3 -5
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/design-connect.d.ts +49 -1
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +414 -9
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/index.js +12 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/plan-local.d.ts +24 -0
- package/dist/cli/plan-local.d.ts.map +1 -1
- package/dist/cli/plan-local.js +188 -20
- package/dist/cli/plan-local.js.map +1 -1
- package/dist/cli/skills.d.ts +3 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +173 -21
- package/dist/cli/skills.js.map +1 -1
- package/dist/cli/templates-meta.d.ts.map +1 -1
- package/dist/cli/templates-meta.js +0 -14
- package/dist/cli/templates-meta.js.map +1 -1
- package/dist/cli/visualize-repo.d.ts +49 -0
- package/dist/cli/visualize-repo.d.ts.map +1 -0
- package/dist/cli/visualize-repo.js +561 -0
- package/dist/cli/visualize-repo.js.map +1 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +120 -15
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +6 -5
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/app-providers.d.ts +2 -2
- package/dist/client/app-providers.js.map +1 -1
- package/dist/client/collab/index.d.ts +4 -1
- package/dist/client/collab/index.d.ts.map +1 -1
- package/dist/client/collab/index.js +3 -0
- package/dist/client/collab/index.js.map +1 -1
- package/dist/client/components/LiveCursorOverlay.d.ts.map +1 -1
- package/dist/client/components/LiveCursorOverlay.js +11 -3
- package/dist/client/components/LiveCursorOverlay.js.map +1 -1
- package/dist/client/components/PresenceBar.d.ts.map +1 -1
- package/dist/client/components/PresenceBar.js +6 -1
- package/dist/client/components/PresenceBar.js.map +1 -1
- package/dist/client/components/RecentEditHighlights.d.ts +43 -0
- package/dist/client/components/RecentEditHighlights.d.ts.map +1 -0
- package/dist/client/components/RecentEditHighlights.js +146 -0
- package/dist/client/components/RecentEditHighlights.js.map +1 -0
- package/dist/client/components/RemoteSelectionRings.d.ts +9 -0
- package/dist/client/components/RemoteSelectionRings.d.ts.map +1 -1
- package/dist/client/components/RemoteSelectionRings.js +22 -8
- package/dist/client/components/RemoteSelectionRings.js.map +1 -1
- package/dist/client/index.d.ts +6 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +7 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
- package/dist/client/org/OrgSwitcher.js +1 -3
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts +8 -1
- package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts.map +1 -1
- package/dist/client/rich-markdown-editor/SharedRichEditor.js +8 -2
- package/dist/client/rich-markdown-editor/SharedRichEditor.js.map +1 -1
- package/dist/client/rich-markdown-editor/index.d.ts +1 -0
- package/dist/client/rich-markdown-editor/index.d.ts.map +1 -1
- package/dist/client/rich-markdown-editor/index.js +1 -0
- package/dist/client/rich-markdown-editor/index.js.map +1 -1
- package/dist/client/rich-markdown-editor/surgical-apply.d.ts +66 -0
- package/dist/client/rich-markdown-editor/surgical-apply.d.ts.map +1 -0
- package/dist/client/rich-markdown-editor/surgical-apply.js +147 -0
- package/dist/client/rich-markdown-editor/surgical-apply.js.map +1 -0
- package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts +18 -2
- package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
- package/dist/client/rich-markdown-editor/useCollabReconcile.js +39 -8
- package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
- package/dist/client/session-replay.d.ts +10 -1
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +134 -7
- package/dist/client/session-replay.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +81 -1
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/client/use-action.d.ts +24 -0
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +109 -27
- package/dist/client/use-action.js.map +1 -1
- package/dist/client/use-agent-engine-configured.d.ts.map +1 -1
- package/dist/client/use-agent-engine-configured.js +7 -1
- package/dist/client/use-agent-engine-configured.js.map +1 -1
- package/dist/client/use-chat-threads.d.ts.map +1 -1
- package/dist/client/use-chat-threads.js +11 -3
- package/dist/client/use-chat-threads.js.map +1 -1
- package/dist/client/use-db-sync.d.ts +36 -0
- package/dist/client/use-db-sync.d.ts.map +1 -1
- package/dist/client/use-db-sync.js +68 -12
- package/dist/client/use-db-sync.js.map +1 -1
- package/dist/client/visual-style-controls.d.ts +79 -0
- package/dist/client/visual-style-controls.d.ts.map +1 -0
- package/dist/client/visual-style-controls.js +150 -0
- package/dist/client/visual-style-controls.js.map +1 -0
- package/dist/collab/agent-presence.d.ts +51 -5
- package/dist/collab/agent-presence.d.ts.map +1 -1
- package/dist/collab/agent-presence.js +144 -58
- package/dist/collab/agent-presence.js.map +1 -1
- package/dist/collab/awareness-store.d.ts +32 -0
- package/dist/collab/awareness-store.d.ts.map +1 -0
- package/dist/collab/awareness-store.js +0 -0
- package/dist/collab/awareness-store.js.map +1 -0
- package/dist/collab/awareness.d.ts +12 -1
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/awareness.js +53 -5
- package/dist/collab/awareness.js.map +1 -1
- package/dist/collab/client.d.ts +2 -0
- package/dist/collab/client.d.ts.map +1 -1
- package/dist/collab/client.js +79 -119
- package/dist/collab/client.js.map +1 -1
- package/dist/collab/index.d.ts +3 -1
- package/dist/collab/index.d.ts.map +1 -1
- package/dist/collab/index.js +5 -1
- package/dist/collab/index.js.map +1 -1
- package/dist/collab/recent-edits.d.ts +87 -0
- package/dist/collab/recent-edits.d.ts.map +1 -0
- package/dist/collab/recent-edits.js +116 -0
- package/dist/collab/recent-edits.js.map +1 -0
- package/dist/collab/routes.d.ts +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/collab/undo.d.ts +121 -0
- package/dist/collab/undo.d.ts.map +1 -0
- package/dist/collab/undo.js +244 -0
- package/dist/collab/undo.js.map +1 -0
- package/dist/collab/ydoc-manager.d.ts +10 -0
- package/dist/collab/ydoc-manager.d.ts.map +1 -1
- package/dist/collab/ydoc-manager.js +57 -0
- package/dist/collab/ydoc-manager.js.map +1 -1
- package/dist/connections/catalog.d.ts +2 -2
- package/dist/connections/catalog.d.ts.map +1 -1
- package/dist/connections/catalog.js +1 -1
- package/dist/connections/catalog.js.map +1 -1
- package/dist/db/create-get-db.d.ts +22 -0
- package/dist/db/create-get-db.d.ts.map +1 -1
- package/dist/db/create-get-db.js +73 -1
- package/dist/db/create-get-db.js.map +1 -1
- package/dist/db/ensure-additive-columns.d.ts +93 -0
- package/dist/db/ensure-additive-columns.d.ts.map +1 -0
- package/dist/db/ensure-additive-columns.js +355 -0
- package/dist/db/ensure-additive-columns.js.map +1 -0
- package/dist/db/index.d.ts +1 -0
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +1 -0
- package/dist/db/index.js.map +1 -1
- package/dist/db/migrations.d.ts +70 -0
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +196 -33
- package/dist/db/migrations.js.map +1 -1
- package/dist/jobs/scheduler.js +17 -10
- package/dist/jobs/scheduler.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +2 -2
- package/dist/observability/traces.d.ts.map +1 -1
- package/dist/observability/traces.js +155 -1
- package/dist/observability/traces.js.map +1 -1
- package/dist/org/context.d.ts +7 -0
- package/dist/org/context.d.ts.map +1 -1
- package/dist/org/context.js +54 -2
- package/dist/org/context.js.map +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +10 -6
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/auth-marketing.d.ts.map +1 -1
- package/dist/server/auth-marketing.js +0 -10
- package/dist/server/auth-marketing.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +6 -4
- package/dist/server/auth.js.map +1 -1
- package/dist/server/collab-plugin.d.ts.map +1 -1
- package/dist/server/collab-plugin.js +45 -13
- package/dist/server/collab-plugin.js.map +1 -1
- package/dist/server/design-token-utils.d.ts +1 -1
- package/dist/server/design-token-utils.js +1 -1
- package/dist/server/design-token-utils.js.map +1 -1
- package/dist/server/fusion-app.d.ts +107 -0
- package/dist/server/fusion-app.d.ts.map +1 -0
- package/dist/server/fusion-app.js +371 -0
- package/dist/server/fusion-app.js.map +1 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/onboarding-html.d.ts.map +1 -1
- package/dist/server/onboarding-html.js +3 -4
- package/dist/server/onboarding-html.js.map +1 -1
- package/dist/server/poll-events.d.ts +2 -0
- package/dist/server/poll-events.d.ts.map +1 -1
- package/dist/server/poll-events.js +7 -2
- package/dist/server/poll-events.js.map +1 -1
- package/dist/server/poll.d.ts +47 -3
- package/dist/server/poll.d.ts.map +1 -1
- package/dist/server/poll.js +176 -10
- package/dist/server/poll.js.map +1 -1
- package/dist/server/schema-prompt.d.ts.map +1 -1
- package/dist/server/schema-prompt.js +24 -7
- package/dist/server/schema-prompt.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/settings/store.d.ts.map +1 -1
- package/dist/settings/store.js +32 -3
- package/dist/settings/store.js.map +1 -1
- package/dist/sharing/actions/set-resource-visibility.js +2 -0
- package/dist/sharing/actions/set-resource-visibility.js.map +1 -1
- package/dist/sharing/actions/share-resource.d.ts.map +1 -1
- package/dist/sharing/actions/share-resource.js +3 -0
- package/dist/sharing/actions/share-resource.js.map +1 -1
- package/dist/sharing/actions/unshare-resource.js +2 -0
- package/dist/sharing/actions/unshare-resource.js.map +1 -1
- package/dist/styles/agent-native.css +0 -5
- package/dist/templates/default/.agents/skills/real-time-collab/SKILL.md +82 -19
- package/dist/templates/default/.agents/skills/real-time-sync/SKILL.md +15 -0
- package/dist/templates/default/.agents/skills/storing-data/SKILL.md +8 -0
- package/dist/templates/default/package.json +3 -3
- package/dist/templates/workspace-core/.agents/skills/client-methods/SKILL.md +8 -0
- package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +22 -0
- package/dist/templates/workspace-core/.agents/skills/real-time-collab/SKILL.md +82 -19
- package/dist/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +15 -0
- package/dist/templates/workspace-core/.agents/skills/storing-data/SKILL.md +8 -0
- package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -0
- package/dist/tracking/providers.d.ts.map +1 -1
- package/dist/tracking/providers.js +17 -1
- package/dist/tracking/providers.js.map +1 -1
- package/docs/content/cloneable-saas.mdx +0 -1
- package/docs/content/locales/ar-SA/cloneable-saas.mdx +0 -1
- package/docs/content/locales/ar-SA/plan-plugin.mdx +10 -9
- package/docs/content/locales/ar-SA/sharing.mdx +1 -2
- package/docs/content/locales/ar-SA/skills-guide.mdx +2 -3
- package/docs/content/locales/ar-SA/template-plan.mdx +23 -7
- package/docs/content/locales/ar-SA/tracking.mdx +1 -1
- package/docs/content/locales/ar-SA/workspace-connections.mdx +11 -11
- package/docs/content/locales/de-DE/cloneable-saas.mdx +0 -1
- package/docs/content/locales/de-DE/plan-plugin.mdx +10 -9
- package/docs/content/locales/de-DE/sharing.mdx +1 -2
- package/docs/content/locales/de-DE/skills-guide.mdx +2 -3
- package/docs/content/locales/de-DE/template-plan.mdx +23 -7
- package/docs/content/locales/de-DE/tracking.mdx +1 -1
- package/docs/content/locales/de-DE/workspace-connections.mdx +1 -1
- package/docs/content/locales/es-ES/cloneable-saas.mdx +0 -1
- package/docs/content/locales/es-ES/plan-plugin.mdx +10 -9
- package/docs/content/locales/es-ES/sharing.mdx +1 -2
- package/docs/content/locales/es-ES/skills-guide.mdx +2 -3
- package/docs/content/locales/es-ES/template-plan.mdx +23 -7
- package/docs/content/locales/es-ES/tracking.mdx +1 -1
- package/docs/content/locales/es-ES/workspace-connections.mdx +1 -1
- package/docs/content/locales/fr-FR/cloneable-saas.mdx +0 -1
- package/docs/content/locales/fr-FR/plan-plugin.mdx +10 -9
- package/docs/content/locales/fr-FR/sharing.mdx +1 -2
- package/docs/content/locales/fr-FR/skills-guide.mdx +2 -3
- package/docs/content/locales/fr-FR/template-plan.mdx +23 -7
- package/docs/content/locales/fr-FR/tracking.mdx +1 -1
- package/docs/content/locales/fr-FR/workspace-connections.mdx +1 -1
- package/docs/content/locales/hi-IN/cloneable-saas.mdx +0 -1
- package/docs/content/locales/hi-IN/plan-plugin.mdx +10 -9
- package/docs/content/locales/hi-IN/sharing.mdx +1 -2
- package/docs/content/locales/hi-IN/skills-guide.mdx +2 -3
- package/docs/content/locales/hi-IN/template-plan.mdx +23 -7
- package/docs/content/locales/hi-IN/tracking.mdx +1 -1
- package/docs/content/locales/hi-IN/workspace-connections.mdx +1 -1
- package/docs/content/locales/ja-JP/cloneable-saas.mdx +0 -1
- package/docs/content/locales/ja-JP/plan-plugin.mdx +10 -9
- package/docs/content/locales/ja-JP/sharing.mdx +1 -2
- package/docs/content/locales/ja-JP/skills-guide.mdx +2 -3
- package/docs/content/locales/ja-JP/template-plan.mdx +23 -7
- package/docs/content/locales/ja-JP/tracking.mdx +1 -1
- package/docs/content/locales/ja-JP/workspace-connections.mdx +1 -1
- package/docs/content/locales/ko-KR/cloneable-saas.mdx +0 -1
- package/docs/content/locales/ko-KR/plan-plugin.mdx +10 -9
- package/docs/content/locales/ko-KR/sharing.mdx +1 -2
- package/docs/content/locales/ko-KR/skills-guide.mdx +2 -3
- package/docs/content/locales/ko-KR/template-plan.mdx +23 -7
- package/docs/content/locales/ko-KR/tracking.mdx +1 -1
- package/docs/content/locales/ko-KR/workspace-connections.mdx +1 -1
- package/docs/content/locales/pt-BR/cloneable-saas.mdx +0 -1
- package/docs/content/locales/pt-BR/plan-plugin.mdx +10 -9
- package/docs/content/locales/pt-BR/sharing.mdx +1 -2
- package/docs/content/locales/pt-BR/skills-guide.mdx +2 -3
- package/docs/content/locales/pt-BR/template-plan.mdx +23 -7
- package/docs/content/locales/pt-BR/tracking.mdx +1 -1
- package/docs/content/locales/pt-BR/workspace-connections.mdx +1 -1
- package/docs/content/locales/zh-CN/cloneable-saas.mdx +0 -1
- package/docs/content/locales/zh-CN/plan-plugin.mdx +10 -9
- package/docs/content/locales/zh-CN/sharing.mdx +1 -2
- package/docs/content/locales/zh-CN/skills-guide.mdx +2 -3
- package/docs/content/locales/zh-CN/template-plan.mdx +23 -7
- package/docs/content/locales/zh-CN/tracking.mdx +1 -1
- package/docs/content/locales/zh-CN/workspace-connections.mdx +1 -1
- package/docs/content/locales/zh-TW/cloneable-saas.mdx +0 -1
- package/docs/content/locales/zh-TW/plan-plugin.mdx +10 -9
- package/docs/content/locales/zh-TW/sharing.mdx +1 -2
- package/docs/content/locales/zh-TW/skills-guide.mdx +2 -3
- package/docs/content/locales/zh-TW/template-plan.mdx +23 -7
- package/docs/content/locales/zh-TW/tracking.mdx +1 -1
- package/docs/content/locales/zh-TW/workspace-connections.mdx +1 -1
- package/docs/content/plan-plugin.mdx +12 -11
- package/docs/content/sharing.mdx +1 -2
- package/docs/content/skills-guide.mdx +4 -3
- package/docs/content/template-plan.mdx +37 -6
- package/docs/content/tracking.mdx +1 -1
- package/docs/content/workspace-connections.mdx +1 -1
- package/package.json +6 -6
- package/src/templates/default/.agents/skills/real-time-collab/SKILL.md +82 -19
- package/src/templates/default/.agents/skills/real-time-sync/SKILL.md +15 -0
- package/src/templates/default/.agents/skills/storing-data/SKILL.md +8 -0
- package/src/templates/default/package.json +3 -3
- package/src/templates/workspace-core/.agents/skills/client-methods/SKILL.md +8 -0
- package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +22 -0
- package/src/templates/workspace-core/.agents/skills/real-time-collab/SKILL.md +82 -19
- package/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +15 -0
- package/src/templates/workspace-core/.agents/skills/storing-data/SKILL.md +8 -0
- package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -0
- package/corpus/core/docs/content/locales/ar-SA/template-videos.mdx +0 -336
- package/corpus/core/docs/content/locales/de-DE/template-videos.mdx +0 -336
- package/corpus/core/docs/content/locales/es-ES/template-videos.mdx +0 -336
- package/corpus/core/docs/content/locales/fr-FR/template-videos.mdx +0 -336
- package/corpus/core/docs/content/locales/hi-IN/template-videos.mdx +0 -336
- package/corpus/core/docs/content/locales/ja-JP/template-videos.mdx +0 -336
- package/corpus/core/docs/content/locales/ko-KR/template-videos.mdx +0 -336
- package/corpus/core/docs/content/locales/pt-BR/template-videos.mdx +0 -336
- package/corpus/core/docs/content/locales/zh-CN/template-videos.mdx +0 -334
- package/corpus/core/docs/content/locales/zh-TW/template-videos.mdx +0 -334
- package/corpus/core/docs/content/template-videos.mdx +0 -332
- package/corpus/templates/design/app/components/design/CodeWorkbenchHost.tsx +0 -697
- package/corpus/templates/slides/app/components/editor/CodeEditor.tsx +0 -49
- package/corpus/templates/videos/.agents/skills/actions/SKILL.md +0 -432
- package/corpus/templates/videos/.agents/skills/animation-tracks/SKILL.md +0 -99
- package/corpus/templates/videos/.agents/skills/capture-learnings/SKILL.md +0 -76
- package/corpus/templates/videos/.agents/skills/composition-management/SKILL.md +0 -106
- package/corpus/templates/videos/.agents/skills/create-skill/SKILL.md +0 -221
- package/corpus/templates/videos/.agents/skills/delegate-to-agent/SKILL.md +0 -263
- package/corpus/templates/videos/.agents/skills/frontend-design/SKILL.md +0 -112
- package/corpus/templates/videos/.agents/skills/security/SKILL.md +0 -263
- package/corpus/templates/videos/.agents/skills/self-modifying-code/SKILL.md +0 -88
- package/corpus/templates/videos/.agents/skills/shadcn-ui/SKILL.md +0 -95
- package/corpus/templates/videos/.builder/ANIMATED_PROPERTIES.md +0 -335
- package/corpus/templates/videos/.builder/KEYFRAME_SYNC.md +0 -166
- package/corpus/templates/videos/.builder/SEAMLESS_SYNC.md +0 -211
- package/corpus/templates/videos/.builder/rules/deploy-app.mdc +0 -22
- package/corpus/templates/videos/.builder/rules/organize-ui.mdc +0 -9
- package/corpus/templates/videos/.builder/skills/remotion/SKILL.md +0 -29
- package/corpus/templates/videos/.builder/skills/remotion/rules/animations.md +0 -27
- package/corpus/templates/videos/.builder/skills/remotion/rules/assets.md +0 -43
- package/corpus/templates/videos/.builder/skills/remotion/rules/audio.md +0 -58
- package/corpus/templates/videos/.builder/skills/remotion/rules/compositions.md +0 -129
- package/corpus/templates/videos/.builder/skills/remotion/rules/fonts.md +0 -47
- package/corpus/templates/videos/.builder/skills/remotion/rules/images.md +0 -41
- package/corpus/templates/videos/.builder/skills/remotion/rules/parameters.md +0 -59
- package/corpus/templates/videos/.builder/skills/remotion/rules/sequencing.md +0 -54
- package/corpus/templates/videos/.builder/skills/remotion/rules/tailwind.md +0 -11
- package/corpus/templates/videos/.builder/skills/remotion/rules/text-animations.md +0 -18
- package/corpus/templates/videos/.builder/skills/remotion/rules/timing.md +0 -103
- package/corpus/templates/videos/.builder/skills/remotion/rules/transparent-videos.md +0 -35
- package/corpus/templates/videos/.builder/skills/remotion/rules/trimming.md +0 -38
- package/corpus/templates/videos/.builder/skills/remotion/rules/videos.md +0 -54
- package/corpus/templates/videos/.dockerignore +0 -62
- package/corpus/templates/videos/.env.example +0 -4
- package/corpus/templates/videos/.oxfmtrc.json +0 -8
- package/corpus/templates/videos/AGENTS.md +0 -46
- package/corpus/templates/videos/CHANGELOG.md +0 -10
- package/corpus/templates/videos/DEVELOPING.md +0 -143
- package/corpus/templates/videos/README.md +0 -33
- package/corpus/templates/videos/actions/analyze-brand-assets.ts +0 -151
- package/corpus/templates/videos/actions/apply-design-system.ts +0 -35
- package/corpus/templates/videos/actions/create-design-system.ts +0 -84
- package/corpus/templates/videos/actions/create-folder.ts +0 -48
- package/corpus/templates/videos/actions/db-connect.ts +0 -45
- package/corpus/templates/videos/actions/db-health.ts +0 -24
- package/corpus/templates/videos/actions/db-status.ts +0 -28
- package/corpus/templates/videos/actions/delete-composition.ts +0 -272
- package/corpus/templates/videos/actions/delete-folder.ts +0 -27
- package/corpus/templates/videos/actions/generate-animated-component.ts +0 -470
- package/corpus/templates/videos/actions/get-composition.ts +0 -36
- package/corpus/templates/videos/actions/get-design-system.ts +0 -35
- package/corpus/templates/videos/actions/import-code.ts +0 -67
- package/corpus/templates/videos/actions/import-design-project.ts +0 -42
- package/corpus/templates/videos/actions/import-document.ts +0 -125
- package/corpus/templates/videos/actions/import-from-url.ts +0 -18
- package/corpus/templates/videos/actions/import-github.ts +0 -182
- package/corpus/templates/videos/actions/list-compositions.ts +0 -32
- package/corpus/templates/videos/actions/list-design-systems.ts +0 -66
- package/corpus/templates/videos/actions/list-folders.ts +0 -61
- package/corpus/templates/videos/actions/move-composition-to-folder.ts +0 -59
- package/corpus/templates/videos/actions/navigate.ts +0 -40
- package/corpus/templates/videos/actions/rename-folder.ts +0 -41
- package/corpus/templates/videos/actions/run.ts +0 -2
- package/corpus/templates/videos/actions/save-composition.ts +0 -114
- package/corpus/templates/videos/actions/set-default-design-system.ts +0 -40
- package/corpus/templates/videos/actions/update-composition.ts +0 -81
- package/corpus/templates/videos/actions/update-design-system.ts +0 -68
- package/corpus/templates/videos/actions/validate-compositions.ts +0 -502
- package/corpus/templates/videos/actions/view-screen.ts +0 -48
- package/corpus/templates/videos/app/components/CameraControls.tsx +0 -442
- package/corpus/templates/videos/app/components/CameraToolbar.tsx +0 -437
- package/corpus/templates/videos/app/components/CloudUpgrade.tsx +0 -287
- package/corpus/templates/videos/app/components/CollabPresenceBar.tsx +0 -81
- package/corpus/templates/videos/app/components/CompSettingsEditor.tsx +0 -107
- package/corpus/templates/videos/app/components/ComponentLibrarySidebar.tsx +0 -457
- package/corpus/templates/videos/app/components/ComponentPropsPanel.tsx +0 -227
- package/corpus/templates/videos/app/components/ComponentsSidebar.tsx +0 -70
- package/corpus/templates/videos/app/components/CompositionCard.tsx +0 -147
- package/corpus/templates/videos/app/components/CurrentElementPanel.tsx +0 -835
- package/corpus/templates/videos/app/components/CursorControls.tsx +0 -664
- package/corpus/templates/videos/app/components/CursorPositioningOverlay.tsx +0 -238
- package/corpus/templates/videos/app/components/LibraryFolderRow.tsx +0 -235
- package/corpus/templates/videos/app/components/MotionCurveSelect.tsx +0 -61
- package/corpus/templates/videos/app/components/NewCompositionPopover.tsx +0 -192
- package/corpus/templates/videos/app/components/PropsEditor.tsx +0 -210
- package/corpus/templates/videos/app/components/QuestionFlow.tsx +0 -40
- package/corpus/templates/videos/app/components/Sidebar.tsx +0 -609
- package/corpus/templates/videos/app/components/StudioHeader.tsx +0 -109
- package/corpus/templates/videos/app/components/Timeline.tsx +0 -2067
- package/corpus/templates/videos/app/components/TrackPropertiesPanel.tsx +0 -922
- package/corpus/templates/videos/app/components/TweaksPanel.tsx +0 -277
- package/corpus/templates/videos/app/components/VideoPlayer.tsx +0 -487
- package/corpus/templates/videos/app/components/design-system/DesignSystemCard.tsx +0 -134
- package/corpus/templates/videos/app/components/design-system/DesignSystemPreview.tsx +0 -176
- package/corpus/templates/videos/app/components/design-system/DesignSystemSetup.tsx +0 -707
- package/corpus/templates/videos/app/components/keyframes/KeyframeActionButtons.tsx +0 -78
- package/corpus/templates/videos/app/components/keyframes/KeyframeNavigation.tsx +0 -59
- package/corpus/templates/videos/app/components/keyframes/PropertyNumberInput.tsx +0 -56
- package/corpus/templates/videos/app/components/layout/Header.tsx +0 -64
- package/corpus/templates/videos/app/components/layout/HeaderActions.tsx +0 -84
- package/corpus/templates/videos/app/components/layout/Layout.tsx +0 -132
- package/corpus/templates/videos/app/components/layout/NavSidebar.tsx +0 -180
- package/corpus/templates/videos/app/components/ui/accordion.tsx +0 -56
- package/corpus/templates/videos/app/components/ui/alert-dialog.tsx +0 -139
- package/corpus/templates/videos/app/components/ui/alert.tsx +0 -59
- package/corpus/templates/videos/app/components/ui/aspect-ratio.tsx +0 -5
- package/corpus/templates/videos/app/components/ui/avatar.tsx +0 -48
- package/corpus/templates/videos/app/components/ui/badge.tsx +0 -37
- package/corpus/templates/videos/app/components/ui/breadcrumb.tsx +0 -115
- package/corpus/templates/videos/app/components/ui/button.tsx +0 -57
- package/corpus/templates/videos/app/components/ui/calendar.tsx +0 -68
- package/corpus/templates/videos/app/components/ui/card.tsx +0 -86
- package/corpus/templates/videos/app/components/ui/carousel.tsx +0 -260
- package/corpus/templates/videos/app/components/ui/chart.tsx +0 -375
- package/corpus/templates/videos/app/components/ui/checkbox.tsx +0 -28
- package/corpus/templates/videos/app/components/ui/collapsible.tsx +0 -9
- package/corpus/templates/videos/app/components/ui/command.tsx +0 -154
- package/corpus/templates/videos/app/components/ui/context-menu.tsx +0 -202
- package/corpus/templates/videos/app/components/ui/dialog.tsx +0 -130
- package/corpus/templates/videos/app/components/ui/drawer.tsx +0 -116
- package/corpus/templates/videos/app/components/ui/dropdown-menu.tsx +0 -212
- package/corpus/templates/videos/app/components/ui/form.tsx +0 -177
- package/corpus/templates/videos/app/components/ui/hover-card.tsx +0 -27
- package/corpus/templates/videos/app/components/ui/input-otp.tsx +0 -69
- package/corpus/templates/videos/app/components/ui/input.tsx +0 -22
- package/corpus/templates/videos/app/components/ui/label.tsx +0 -24
- package/corpus/templates/videos/app/components/ui/menubar.tsx +0 -234
- package/corpus/templates/videos/app/components/ui/navigation-menu.tsx +0 -128
- package/corpus/templates/videos/app/components/ui/pagination.tsx +0 -121
- package/corpus/templates/videos/app/components/ui/popover.tsx +0 -44
- package/corpus/templates/videos/app/components/ui/progress.tsx +0 -26
- package/corpus/templates/videos/app/components/ui/radio-group.tsx +0 -42
- package/corpus/templates/videos/app/components/ui/resizable.tsx +0 -43
- package/corpus/templates/videos/app/components/ui/scroll-area.tsx +0 -46
- package/corpus/templates/videos/app/components/ui/select.tsx +0 -158
- package/corpus/templates/videos/app/components/ui/separator.tsx +0 -29
- package/corpus/templates/videos/app/components/ui/sheet.tsx +0 -160
- package/corpus/templates/videos/app/components/ui/sidebar.tsx +0 -782
- package/corpus/templates/videos/app/components/ui/skeleton.tsx +0 -15
- package/corpus/templates/videos/app/components/ui/slider.tsx +0 -26
- package/corpus/templates/videos/app/components/ui/sonner.tsx +0 -58
- package/corpus/templates/videos/app/components/ui/spinner.tsx +0 -17
- package/corpus/templates/videos/app/components/ui/switch.tsx +0 -27
- package/corpus/templates/videos/app/components/ui/table.tsx +0 -117
- package/corpus/templates/videos/app/components/ui/tabs.tsx +0 -53
- package/corpus/templates/videos/app/components/ui/textarea.tsx +0 -23
- package/corpus/templates/videos/app/components/ui/toast.tsx +0 -127
- package/corpus/templates/videos/app/components/ui/toaster.tsx +0 -33
- package/corpus/templates/videos/app/components/ui/toggle-group.tsx +0 -59
- package/corpus/templates/videos/app/components/ui/toggle.tsx +0 -43
- package/corpus/templates/videos/app/components/ui/tooltip.tsx +0 -30
- package/corpus/templates/videos/app/components/ui/use-toast.ts +0 -3
- package/corpus/templates/videos/app/config/constants.ts +0 -204
- package/corpus/templates/videos/app/contexts/CompositionContext.tsx +0 -437
- package/corpus/templates/videos/app/contexts/CurrentElementContext.tsx +0 -247
- package/corpus/templates/videos/app/contexts/PlaybackContext.tsx +0 -65
- package/corpus/templates/videos/app/contexts/TimelineContext.tsx +0 -433
- package/corpus/templates/videos/app/entry.client.tsx +0 -19
- package/corpus/templates/videos/app/entry.server.tsx +0 -10
- package/corpus/templates/videos/app/global.css +0 -171
- package/corpus/templates/videos/app/hooks/use-composition-collab.ts +0 -269
- package/corpus/templates/videos/app/hooks/use-composition-design-system.ts +0 -47
- package/corpus/templates/videos/app/hooks/use-database-compositions.ts +0 -144
- package/corpus/templates/videos/app/hooks/use-db-status.ts +0 -30
- package/corpus/templates/videos/app/hooks/use-design-systems.ts +0 -21
- package/corpus/templates/videos/app/hooks/use-folders.ts +0 -199
- package/corpus/templates/videos/app/hooks/use-mobile.tsx +0 -21
- package/corpus/templates/videos/app/hooks/use-navigation-state.ts +0 -52
- package/corpus/templates/videos/app/hooks/use-question-flow.ts +0 -14
- package/corpus/templates/videos/app/hooks/use-toast.ts +0 -188
- package/corpus/templates/videos/app/hooks/useUnsavedChanges.ts +0 -48
- package/corpus/templates/videos/app/i18n/ar-SA.ts +0 -446
- package/corpus/templates/videos/app/i18n/de-DE.ts +0 -470
- package/corpus/templates/videos/app/i18n/en-US.ts +0 -452
- package/corpus/templates/videos/app/i18n/es-ES.ts +0 -468
- package/corpus/templates/videos/app/i18n/fr-FR.ts +0 -469
- package/corpus/templates/videos/app/i18n/hi-IN.ts +0 -446
- package/corpus/templates/videos/app/i18n/index.ts +0 -34
- package/corpus/templates/videos/app/i18n/ja-JP.ts +0 -460
- package/corpus/templates/videos/app/i18n/ko-KR.ts +0 -453
- package/corpus/templates/videos/app/i18n/pt-BR.ts +0 -461
- package/corpus/templates/videos/app/i18n/zh-CN.ts +0 -437
- package/corpus/templates/videos/app/i18n/zh-TW.ts +0 -437
- package/corpus/templates/videos/app/lib/database-compositions.ts +0 -67
- package/corpus/templates/videos/app/lib/design-systems.ts +0 -234
- package/corpus/templates/videos/app/lib/tab-id.ts +0 -1
- package/corpus/templates/videos/app/lib/utils.ts +0 -1
- package/corpus/templates/videos/app/pages/ComponentLibrary.tsx +0 -134
- package/corpus/templates/videos/app/pages/ComponentLibraryView.tsx +0 -362
- package/corpus/templates/videos/app/pages/CompositionView.tsx +0 -715
- package/corpus/templates/videos/app/pages/DesignSystems.tsx +0 -180
- package/corpus/templates/videos/app/pages/Index.tsx +0 -258
- package/corpus/templates/videos/app/pages/NewComposition.tsx +0 -95
- package/corpus/templates/videos/app/pages/NotFound.tsx +0 -36
- package/corpus/templates/videos/app/remotion/CameraHost.tsx +0 -223
- package/corpus/templates/videos/app/remotion/animations/useElementAnimations.ts +0 -231
- package/corpus/templates/videos/app/remotion/componentRegistry.ts +0 -473
- package/corpus/templates/videos/app/remotion/components/AnimatedElement.tsx +0 -436
- package/corpus/templates/videos/app/remotion/components/InteractiveElement.tsx +0 -159
- package/corpus/templates/videos/app/remotion/components/InteractiveZoneEditor.tsx +0 -273
- package/corpus/templates/videos/app/remotion/compositions/BlankComposition.tsx +0 -204
- package/corpus/templates/videos/app/remotion/compositions/index.ts +0 -2
- package/corpus/templates/videos/app/remotion/createComponentCursorTrack.ts +0 -132
- package/corpus/templates/videos/app/remotion/docs/CURSOR_TRACKS.md +0 -285
- package/corpus/templates/videos/app/remotion/easingFunctions.ts +0 -255
- package/corpus/templates/videos/app/remotion/hooks/createInteractiveComposition.tsx +0 -283
- package/corpus/templates/videos/app/remotion/hooks/useCursorHistory.ts +0 -72
- package/corpus/templates/videos/app/remotion/hooks/useCursorTypeFromHover.ts +0 -35
- package/corpus/templates/videos/app/remotion/hooks/useEditableZones.tsx +0 -267
- package/corpus/templates/videos/app/remotion/hooks/useHoverAnimation.ts +0 -90
- package/corpus/templates/videos/app/remotion/hooks/useHoverAnimationSmooth.ts +0 -146
- package/corpus/templates/videos/app/remotion/hooks/useInteractiveComponent.ts +0 -711
- package/corpus/templates/videos/app/remotion/hooks/useRegisterInteractiveElement.ts +0 -56
- package/corpus/templates/videos/app/remotion/library-components/Button.tsx +0 -252
- package/corpus/templates/videos/app/remotion/library-components/Card.tsx +0 -241
- package/corpus/templates/videos/app/remotion/library-components/CodePanel.tsx +0 -1005
- package/corpus/templates/videos/app/remotion/library-components/FileItem.tsx +0 -196
- package/corpus/templates/videos/app/remotion/library-components/FolderItem.tsx +0 -199
- package/corpus/templates/videos/app/remotion/library-components/PrimaryButton.tsx +0 -177
- package/corpus/templates/videos/app/remotion/library-components/SecondaryButton.tsx +0 -167
- package/corpus/templates/videos/app/remotion/library-components/SectionHeader.tsx +0 -108
- package/corpus/templates/videos/app/remotion/library-components/TEMPLATE.tsx +0 -211
- package/corpus/templates/videos/app/remotion/library-components/index.ts +0 -10
- package/corpus/templates/videos/app/remotion/registry.ts +0 -91
- package/corpus/templates/videos/app/remotion/testing/animationTestUtils.ts +0 -366
- package/corpus/templates/videos/app/remotion/trackAnimation.ts +0 -179
- package/corpus/templates/videos/app/remotion/trackHelpers.ts +0 -110
- package/corpus/templates/videos/app/remotion/ui-components/Cursor.tsx +0 -182
- package/corpus/templates/videos/app/remotion/ui-components/InteractiveButton.tsx +0 -116
- package/corpus/templates/videos/app/remotion/ui-components/InteractiveCard.tsx +0 -126
- package/corpus/templates/videos/app/remotion/ui-components/index.ts +0 -16
- package/corpus/templates/videos/app/remotion/utils/animationHelpers.ts +0 -444
- package/corpus/templates/videos/app/remotion/utils/interactiveElements.ts +0 -235
- package/corpus/templates/videos/app/remotion/utils/springAnimation.ts +0 -76
- package/corpus/templates/videos/app/root.tsx +0 -165
- package/corpus/templates/videos/app/routes/$.tsx +0 -10
- package/corpus/templates/videos/app/routes/_index.tsx +0 -25
- package/corpus/templates/videos/app/routes/c.$compositionId.tsx +0 -10
- package/corpus/templates/videos/app/routes/components.tsx +0 -10
- package/corpus/templates/videos/app/routes/design-systems.tsx +0 -10
- package/corpus/templates/videos/app/routes/extensions.$id.$slug.tsx +0 -2
- package/corpus/templates/videos/app/routes/extensions.$id.tsx +0 -5
- package/corpus/templates/videos/app/routes/extensions._index.tsx +0 -5
- package/corpus/templates/videos/app/routes/extensions.tsx +0 -5
- package/corpus/templates/videos/app/routes/settings.tsx +0 -88
- package/corpus/templates/videos/app/routes/team.tsx +0 -11
- package/corpus/templates/videos/app/routes.ts +0 -4
- package/corpus/templates/videos/app/state.ts +0 -77
- package/corpus/templates/videos/app/types/elementAnimations.ts +0 -121
- package/corpus/templates/videos/app/types.ts +0 -200
- package/corpus/templates/videos/app/utils/compositionHelpers.ts +0 -445
- package/corpus/templates/videos/app/utils/debug.ts +0 -63
- package/corpus/templates/videos/app/utils/keyframeUtils.ts +0 -331
- package/corpus/templates/videos/app/utils/timelineCoordinates.ts +0 -79
- package/corpus/templates/videos/app/vite-env.d.ts +0 -6
- package/corpus/templates/videos/changelog/2026-06-24-a-new-settings-page-gives-quick-access-to-language-workspace.md +0 -6
- package/corpus/templates/videos/changelog/2026-06-24-added-a-language-picker-and-localized-app-chrome-for-support.md +0 -6
- package/corpus/templates/videos/changelog/2026-06-27-traditional-chinese-copy-uses-taiwan-terminology.md +0 -6
- package/corpus/templates/videos/changelog/2026-06-28-left-sidebar-collapses-into-an-animated-icon-rail.md +0 -6
- package/corpus/templates/videos/changelog/2026-06-29-video-layouts-adapt-when-the-agent-sidebar-is-open.md +0 -6
- package/corpus/templates/videos/components.json +0 -20
- package/corpus/templates/videos/data/sync-config.json +0 -3
- package/corpus/templates/videos/docs/examples/create-composition.example.ts +0 -128
- package/corpus/templates/videos/drizzle.config.ts +0 -3
- package/corpus/templates/videos/learnings.defaults.md +0 -5
- package/corpus/templates/videos/package.json +0 -114
- package/corpus/templates/videos/public/agent-native-icon-dark.svg +0 -10
- package/corpus/templates/videos/public/agent-native-icon-light.svg +0 -10
- package/corpus/templates/videos/public/agent-native-logo-dark.svg +0 -21
- package/corpus/templates/videos/public/agent-native-logo-light.svg +0 -21
- package/corpus/templates/videos/public/favicon.svg +0 -1
- package/corpus/templates/videos/public/icon-180.svg +0 -1
- package/corpus/templates/videos/public/icon-192.svg +0 -1
- package/corpus/templates/videos/public/icon-512.svg +0 -1
- package/corpus/templates/videos/public/manifest.json +0 -21
- package/corpus/templates/videos/public/placeholder.svg +0 -1
- package/corpus/templates/videos/public/robots.txt +0 -14
- package/corpus/templates/videos/react-router.config.ts +0 -7
- package/corpus/templates/videos/server/db/index.ts +0 -37
- package/corpus/templates/videos/server/db/schema.ts +0 -55
- package/corpus/templates/videos/server/middleware/auth.ts +0 -15
- package/corpus/templates/videos/server/plugins/agent-chat.ts +0 -29
- package/corpus/templates/videos/server/plugins/auth.ts +0 -14
- package/corpus/templates/videos/server/plugins/collab.ts +0 -11
- package/corpus/templates/videos/server/plugins/core-routes.ts +0 -22
- package/corpus/templates/videos/server/plugins/db.ts +0 -160
- package/corpus/templates/videos/server/routes/[...page].get.ts +0 -5
- package/corpus/templates/videos/server/routes/[...page].head.ts +0 -1
- package/corpus/templates/videos/server/routes/api/save-composition-defaults.post.ts +0 -274
- package/corpus/templates/videos/shared/api.ts +0 -75
- package/corpus/templates/videos/ssr-entry.ts +0 -15
- package/corpus/templates/videos/tsconfig.json +0 -21
- package/corpus/templates/videos/vite.config.ts +0 -19
- package/corpus/templates/videos/vitest.config.ts +0 -16
- package/docs/content/locales/ar-SA/template-videos.mdx +0 -336
- package/docs/content/locales/de-DE/template-videos.mdx +0 -336
- package/docs/content/locales/es-ES/template-videos.mdx +0 -336
- package/docs/content/locales/fr-FR/template-videos.mdx +0 -336
- package/docs/content/locales/hi-IN/template-videos.mdx +0 -336
- package/docs/content/locales/ja-JP/template-videos.mdx +0 -336
- package/docs/content/locales/ko-KR/template-videos.mdx +0 -336
- package/docs/content/locales/pt-BR/template-videos.mdx +0 -336
- package/docs/content/locales/zh-CN/template-videos.mdx +0 -334
- package/docs/content/locales/zh-TW/template-videos.mdx +0 -334
- package/docs/content/template-videos.mdx +0 -332
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
|
+
agentNativePath,
|
|
2
3
|
appBasePath,
|
|
3
4
|
callAction,
|
|
5
|
+
createLocalOpUndoController,
|
|
4
6
|
isEmbedAuthActive,
|
|
7
|
+
type LocalOpUndoController,
|
|
8
|
+
type LocalOpUndoEntry,
|
|
5
9
|
} from "@agent-native/core/client";
|
|
6
10
|
import { nanoid } from "nanoid";
|
|
7
11
|
import {
|
|
@@ -28,7 +32,7 @@ type GranularOp =
|
|
|
28
32
|
slideId: string;
|
|
29
33
|
fields: Partial<Omit<Slide, "id">>;
|
|
30
34
|
}
|
|
31
|
-
| { op: "delete-slide"; slideId: string }
|
|
35
|
+
| { op: "delete-slide"; slideId: string; allowEmpty?: boolean }
|
|
32
36
|
| { op: "reorder-slides"; orderedIds: string[] }
|
|
33
37
|
| {
|
|
34
38
|
op: "add-slide";
|
|
@@ -50,7 +54,19 @@ type GranularOp =
|
|
|
50
54
|
/** Sentinel: discard all accumulated ops and do a full PUT instead. */
|
|
51
55
|
| { op: "full-replace"; deck: Deck };
|
|
52
56
|
|
|
53
|
-
type PatchDeckOp = Exclude<GranularOp, { op: "full-replace" }>;
|
|
57
|
+
export type PatchDeckOp = Exclude<GranularOp, { op: "full-replace" }>;
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Inverse-op undo
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// Undo/redo is per-user and is granular for ordinary slide/deck-field edits.
|
|
63
|
+
// Deck lifecycle and generated/imported full replacements use explicit
|
|
64
|
+
// deck-level ops because those user actions are whole-resource mutations.
|
|
65
|
+
export type DeckUndoOp =
|
|
66
|
+
| ({ deckId: string } & PatchDeckOp)
|
|
67
|
+
| { op: "delete-deck"; deckId: string }
|
|
68
|
+
| { op: "restore-deck"; deckId: string; deck: Deck; index?: number }
|
|
69
|
+
| { op: "replace-deck"; deckId: string; deck: Deck };
|
|
54
70
|
|
|
55
71
|
export type SlideLayout =
|
|
56
72
|
| "title"
|
|
@@ -115,12 +131,6 @@ export interface Deck {
|
|
|
115
131
|
aspectRatio?: AspectRatio;
|
|
116
132
|
}
|
|
117
133
|
|
|
118
|
-
export interface HistoryEntry {
|
|
119
|
-
timestamp: number;
|
|
120
|
-
label: string;
|
|
121
|
-
decks: Deck[];
|
|
122
|
-
}
|
|
123
|
-
|
|
124
134
|
interface DeckContextType {
|
|
125
135
|
decks: Deck[];
|
|
126
136
|
loading: boolean;
|
|
@@ -170,19 +180,15 @@ interface DeckContextType {
|
|
|
170
180
|
* in-progress edit.
|
|
171
181
|
*/
|
|
172
182
|
markDeckDirty: (deckId: string) => void;
|
|
173
|
-
// Undo/Redo
|
|
183
|
+
// Undo/Redo — per-user inverse-op undo (see DeckUndoOp above).
|
|
174
184
|
undo: () => void;
|
|
175
185
|
redo: () => void;
|
|
176
186
|
canUndo: boolean;
|
|
177
187
|
canRedo: boolean;
|
|
178
|
-
history: HistoryEntry[];
|
|
179
|
-
historyIndex: number;
|
|
180
|
-
restoreFromHistory: (index: number) => void;
|
|
181
188
|
}
|
|
182
189
|
|
|
183
190
|
const DeckContext = createContext<DeckContextType | null>(null);
|
|
184
191
|
|
|
185
|
-
const MAX_HISTORY = 50;
|
|
186
192
|
const OPEN_DECK_FALLBACK_POLL_MS = 5_000;
|
|
187
193
|
const DECK_LIST_FALLBACK_POLL_MS = 15_000;
|
|
188
194
|
|
|
@@ -268,12 +274,15 @@ export function getSaveSnapshot(): { saving: boolean } {
|
|
|
268
274
|
*
|
|
269
275
|
* When a `full-replace` op is enqueued, all previously-queued ops for that
|
|
270
276
|
* deck are discarded because the full replace already captures the authoritative
|
|
271
|
-
* state (used by undo/redo and bulk generation which produce a
|
|
272
|
-
* snapshot).
|
|
277
|
+
* state at that moment (used by undo/redo and bulk generation which produce a
|
|
278
|
+
* known good snapshot). Later granular edits inside the same debounce window
|
|
279
|
+
* must still be appended after that snapshot so quick follow-up user edits are
|
|
280
|
+
* not dropped on reload.
|
|
273
281
|
*
|
|
274
282
|
* The debounce fires after 500 ms of quiet, draining the queue via the
|
|
275
|
-
* granular `patch-deck` action. If the queue
|
|
276
|
-
* a direct PUT to `/api/decks/:id` is used
|
|
283
|
+
* granular `patch-deck` action. If the queue starts with a `full-replace` op,
|
|
284
|
+
* a direct PUT to `/api/decks/:id` is used first, then any trailing granular
|
|
285
|
+
* ops are sent through `patch-deck`.
|
|
277
286
|
*/
|
|
278
287
|
function enqueueDeckOp(deckId: string, op: GranularOp) {
|
|
279
288
|
// Clear any pending save timer — we're about to reset it
|
|
@@ -285,17 +294,8 @@ function enqueueDeckOp(deckId: string, op: GranularOp) {
|
|
|
285
294
|
pendingOpsQueue.set(deckId, [op]);
|
|
286
295
|
} else {
|
|
287
296
|
const queue = pendingOpsQueue.get(deckId) ?? [];
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
// Replace the stored deck snapshot with the latest state by replacing
|
|
291
|
-
// the full-replace op rather than appending more granular ops on top.
|
|
292
|
-
// (The op already carries the full deck; newer state comes via the
|
|
293
|
-
// dirty-deck save effect which will enqueue a fresh full-replace when
|
|
294
|
-
// it runs.)
|
|
295
|
-
} else {
|
|
296
|
-
queue.push(op);
|
|
297
|
-
pendingOpsQueue.set(deckId, queue);
|
|
298
|
-
}
|
|
297
|
+
queue.push(op);
|
|
298
|
+
pendingOpsQueue.set(deckId, queue);
|
|
299
299
|
}
|
|
300
300
|
|
|
301
301
|
// Arm the debounce
|
|
@@ -318,6 +318,13 @@ function enqueueDeckOp(deckId: string, op: GranularOp) {
|
|
|
318
318
|
headers: { "Content-Type": "application/json" },
|
|
319
319
|
body: JSON.stringify(deck),
|
|
320
320
|
});
|
|
321
|
+
const trailingOps = ops.slice(1) as PatchDeckOp[];
|
|
322
|
+
if (trailingOps.length > 0) {
|
|
323
|
+
await callAction("patch-deck", {
|
|
324
|
+
deckId,
|
|
325
|
+
operations: trailingOps,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
321
328
|
} else {
|
|
322
329
|
// Granular patch — concurrent-safe
|
|
323
330
|
await callAction("patch-deck", {
|
|
@@ -346,6 +353,291 @@ function saveDeckToAPI(deck: Deck) {
|
|
|
346
353
|
enqueueDeckOp(deck.id, { op: "full-replace", deck });
|
|
347
354
|
}
|
|
348
355
|
|
|
356
|
+
/**
|
|
357
|
+
* Synchronously flush every pending (debounced) deck op queue using
|
|
358
|
+
* `fetch(..., { keepalive: true })` so in-flight edits survive a tab
|
|
359
|
+
* close / navigation. Called from a `pagehide` / `visibilitychange(hidden)`
|
|
360
|
+
* handler — without it there is a ~500ms window (the debounce) where the
|
|
361
|
+
* user's most recent edits are only in memory and are lost on tab close.
|
|
362
|
+
*
|
|
363
|
+
* keepalive requests are best-effort and capped (~64KB by the browser), which
|
|
364
|
+
* is fine: granular ops are small, and if a full-replace payload is too large
|
|
365
|
+
* to send keepalive the normal debounce/poll path still catches up on reopen.
|
|
366
|
+
*/
|
|
367
|
+
function flushPendingSaves() {
|
|
368
|
+
const actionUrl = `${agentNativePath("/_agent-native/actions")}/patch-deck`;
|
|
369
|
+
for (const [deckId, timer] of pendingSaves) {
|
|
370
|
+
clearTimeout(timer);
|
|
371
|
+
const ops = pendingOpsQueue.get(deckId);
|
|
372
|
+
pendingOpsQueue.delete(deckId);
|
|
373
|
+
if (!ops || ops.length === 0) continue;
|
|
374
|
+
try {
|
|
375
|
+
if (ops[0].op === "full-replace") {
|
|
376
|
+
const deck = ops[0].deck;
|
|
377
|
+
void fetch(`${appBasePath()}/api/decks/${deckId}`, {
|
|
378
|
+
method: "PUT",
|
|
379
|
+
headers: { "Content-Type": "application/json" },
|
|
380
|
+
body: JSON.stringify(deck),
|
|
381
|
+
keepalive: true,
|
|
382
|
+
});
|
|
383
|
+
const trailingOps = ops.slice(1) as PatchDeckOp[];
|
|
384
|
+
if (trailingOps.length === 0) continue;
|
|
385
|
+
void fetch(actionUrl, {
|
|
386
|
+
method: "POST",
|
|
387
|
+
headers: {
|
|
388
|
+
"Content-Type": "application/json",
|
|
389
|
+
"X-Agent-Native-Frontend": "1",
|
|
390
|
+
},
|
|
391
|
+
body: JSON.stringify({
|
|
392
|
+
deckId,
|
|
393
|
+
operations: trailingOps,
|
|
394
|
+
}),
|
|
395
|
+
keepalive: true,
|
|
396
|
+
});
|
|
397
|
+
} else {
|
|
398
|
+
void fetch(actionUrl, {
|
|
399
|
+
method: "POST",
|
|
400
|
+
headers: {
|
|
401
|
+
"Content-Type": "application/json",
|
|
402
|
+
"X-Agent-Native-Frontend": "1",
|
|
403
|
+
},
|
|
404
|
+
body: JSON.stringify({
|
|
405
|
+
deckId,
|
|
406
|
+
operations: ops as PatchDeckOp[],
|
|
407
|
+
}),
|
|
408
|
+
keepalive: true,
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
} catch {
|
|
412
|
+
// Best-effort — nothing more we can do as the page is unloading.
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
pendingSaves.clear();
|
|
416
|
+
notifySaveListeners();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function discardPendingDeckOps(deckId: string) {
|
|
420
|
+
const timer = pendingSaves.get(deckId);
|
|
421
|
+
if (timer) clearTimeout(timer);
|
|
422
|
+
pendingSaves.delete(deckId);
|
|
423
|
+
pendingOpsQueue.delete(deckId);
|
|
424
|
+
notifySaveListeners();
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// ---------------------------------------------------------------------------
|
|
428
|
+
// Local op application + inverse derivation (for inverse-op undo)
|
|
429
|
+
// ---------------------------------------------------------------------------
|
|
430
|
+
// These mirror the server-side merge in actions/patch-deck.ts but operate on
|
|
431
|
+
// the in-memory Deck[] so undo/redo can apply optimistically. They are pure:
|
|
432
|
+
// they return a new slides array / deck rather than mutating in place.
|
|
433
|
+
|
|
434
|
+
/** Fields carried by a `patch-deck-fields` op. */
|
|
435
|
+
type PatchDeckFields = Extract<
|
|
436
|
+
PatchDeckOp,
|
|
437
|
+
{ op: "patch-deck-fields" }
|
|
438
|
+
>["fields"];
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Apply a single granular op to a deck's slides/fields, returning the updated
|
|
442
|
+
* Deck. Unknown/no-op cases (slide already gone, etc.) return the deck
|
|
443
|
+
* unchanged so undo entries that no longer apply fail soft instead of
|
|
444
|
+
* corrupting state.
|
|
445
|
+
*/
|
|
446
|
+
export function applyOpToDeck(deck: Deck, op: PatchDeckOp): Deck {
|
|
447
|
+
switch (op.op) {
|
|
448
|
+
case "patch-slide": {
|
|
449
|
+
let changed = false;
|
|
450
|
+
const slides = deck.slides.map((s) => {
|
|
451
|
+
if (s.id !== op.slideId) return s;
|
|
452
|
+
changed = true;
|
|
453
|
+
return { ...s, ...op.fields };
|
|
454
|
+
});
|
|
455
|
+
if (!changed) return deck; // slide concurrently deleted — skip
|
|
456
|
+
return { ...deck, slides, updatedAt: new Date().toISOString() };
|
|
457
|
+
}
|
|
458
|
+
case "delete-slide": {
|
|
459
|
+
const slides = deck.slides.filter((s) => s.id !== op.slideId);
|
|
460
|
+
if (slides.length === deck.slides.length) return deck; // already gone
|
|
461
|
+
// NOTE: unlike the user-facing `deleteSlide` handler and the server merge,
|
|
462
|
+
// undo/redo application does NOT inject a fallback blank slide when the
|
|
463
|
+
// deck empties out. Undo must restore the EXACT prior state — if the deck
|
|
464
|
+
// was legitimately empty before an add-slide (e.g. a freshly reloaded
|
|
465
|
+
// empty deck), undoing that add must return it to empty, not to a
|
|
466
|
+
// spurious blank slide.
|
|
467
|
+
return { ...deck, slides, updatedAt: new Date().toISOString() };
|
|
468
|
+
}
|
|
469
|
+
case "reorder-slides": {
|
|
470
|
+
const byId = new Map(deck.slides.map((s) => [s.id, s]));
|
|
471
|
+
const reordered: Slide[] = [];
|
|
472
|
+
for (const id of op.orderedIds) {
|
|
473
|
+
const slide = byId.get(id);
|
|
474
|
+
if (slide) reordered.push(slide);
|
|
475
|
+
}
|
|
476
|
+
// Preserve slides not named in orderedIds (concurrent adds) at the end.
|
|
477
|
+
const named = new Set(op.orderedIds);
|
|
478
|
+
for (const s of deck.slides) {
|
|
479
|
+
if (!named.has(s.id)) reordered.push(s);
|
|
480
|
+
}
|
|
481
|
+
return {
|
|
482
|
+
...deck,
|
|
483
|
+
slides: reordered,
|
|
484
|
+
updatedAt: new Date().toISOString(),
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
case "add-slide": {
|
|
488
|
+
if (deck.slides.some((s) => s.id === op.slideId)) return deck; // idempotent
|
|
489
|
+
const newSlide: Slide = {
|
|
490
|
+
id: op.slideId,
|
|
491
|
+
content: op.fields.content,
|
|
492
|
+
notes: op.fields.notes ?? "",
|
|
493
|
+
layout: (op.fields.layout as SlideLayout) ?? "content",
|
|
494
|
+
...(op.fields.background !== undefined
|
|
495
|
+
? { background: op.fields.background }
|
|
496
|
+
: {}),
|
|
497
|
+
};
|
|
498
|
+
const slides = [...deck.slides];
|
|
499
|
+
const afterIdx = op.afterSlideId
|
|
500
|
+
? slides.findIndex((s) => s.id === op.afterSlideId)
|
|
501
|
+
: -1;
|
|
502
|
+
if (afterIdx !== -1) slides.splice(afterIdx + 1, 0, newSlide);
|
|
503
|
+
else slides.push(newSlide);
|
|
504
|
+
return { ...deck, slides, updatedAt: new Date().toISOString() };
|
|
505
|
+
}
|
|
506
|
+
case "patch-deck-fields": {
|
|
507
|
+
return {
|
|
508
|
+
...deck,
|
|
509
|
+
...op.fields,
|
|
510
|
+
updatedAt: new Date().toISOString(),
|
|
511
|
+
} as Deck;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
export function applyUndoOpToDecks(decks: Deck[], op: DeckUndoOp): Deck[] {
|
|
517
|
+
switch (op.op) {
|
|
518
|
+
case "delete-deck":
|
|
519
|
+
return decks.filter((deck) => deck.id !== op.deckId);
|
|
520
|
+
case "restore-deck": {
|
|
521
|
+
const nextDeck = op.deck;
|
|
522
|
+
const existingIndex = decks.findIndex((deck) => deck.id === op.deckId);
|
|
523
|
+
if (existingIndex >= 0) {
|
|
524
|
+
const next = [...decks];
|
|
525
|
+
next[existingIndex] = nextDeck;
|
|
526
|
+
return next;
|
|
527
|
+
}
|
|
528
|
+
const next = [...decks];
|
|
529
|
+
const index =
|
|
530
|
+
typeof op.index === "number" && op.index >= 0
|
|
531
|
+
? Math.min(op.index, next.length)
|
|
532
|
+
: next.length;
|
|
533
|
+
next.splice(index, 0, nextDeck);
|
|
534
|
+
return next;
|
|
535
|
+
}
|
|
536
|
+
case "replace-deck": {
|
|
537
|
+
const existingIndex = decks.findIndex((deck) => deck.id === op.deckId);
|
|
538
|
+
if (existingIndex < 0) return [...decks, op.deck];
|
|
539
|
+
const next = [...decks];
|
|
540
|
+
next[existingIndex] = op.deck;
|
|
541
|
+
return next;
|
|
542
|
+
}
|
|
543
|
+
default: {
|
|
544
|
+
const idx = decks.findIndex((deck) => deck.id === op.deckId);
|
|
545
|
+
if (idx < 0) return decks;
|
|
546
|
+
const { deckId: _deckId, ...granular } = op;
|
|
547
|
+
void _deckId;
|
|
548
|
+
const updated = applyOpToDeck(decks[idx], granular);
|
|
549
|
+
if (updated === decks[idx]) return decks;
|
|
550
|
+
const next = [...decks];
|
|
551
|
+
next[idx] = updated;
|
|
552
|
+
return next;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Build the inverse of a granular op given the deck state BEFORE the op was
|
|
559
|
+
* applied. Returns an array of ops to apply (usually one, occasionally two) or
|
|
560
|
+
* `null` when the op has no meaningful inverse (e.g. a no-op patch) so the
|
|
561
|
+
* caller skips pushing an undo entry.
|
|
562
|
+
*/
|
|
563
|
+
export function deriveInverseOp(
|
|
564
|
+
before: Deck,
|
|
565
|
+
op: PatchDeckOp,
|
|
566
|
+
): PatchDeckOp[] | null {
|
|
567
|
+
switch (op.op) {
|
|
568
|
+
case "patch-slide": {
|
|
569
|
+
const prior = before.slides.find((s) => s.id === op.slideId);
|
|
570
|
+
if (!prior) return null; // slide didn't exist before — nothing to restore
|
|
571
|
+
const priorFields: Partial<Omit<Slide, "id">> = {};
|
|
572
|
+
for (const key of Object.keys(op.fields) as (keyof Omit<Slide, "id">)[]) {
|
|
573
|
+
// Capture the prior value for every field this op touches, so undo
|
|
574
|
+
// restores exactly what changed (including clearing fields back to
|
|
575
|
+
// undefined).
|
|
576
|
+
(priorFields as Record<string, unknown>)[key] = prior[key];
|
|
577
|
+
}
|
|
578
|
+
return [{ op: "patch-slide", slideId: op.slideId, fields: priorFields }];
|
|
579
|
+
}
|
|
580
|
+
case "delete-slide": {
|
|
581
|
+
const prior = before.slides.find((s) => s.id === op.slideId);
|
|
582
|
+
if (!prior) return null;
|
|
583
|
+
const idx = before.slides.findIndex((s) => s.id === op.slideId);
|
|
584
|
+
const afterSlideId = idx > 0 ? before.slides[idx - 1]?.id : undefined;
|
|
585
|
+
// Re-add the deleted slide with its full prior content, then reorder to
|
|
586
|
+
// the exact prior order. The add-slide op alone can only express "after
|
|
587
|
+
// slide X" or "append", so it cannot restore a slide to the HEAD of the
|
|
588
|
+
// deck; the follow-up reorder guarantees exact position regardless.
|
|
589
|
+
return [
|
|
590
|
+
{
|
|
591
|
+
op: "add-slide",
|
|
592
|
+
slideId: prior.id,
|
|
593
|
+
afterSlideId,
|
|
594
|
+
fields: {
|
|
595
|
+
content: prior.content,
|
|
596
|
+
notes: prior.notes,
|
|
597
|
+
layout: prior.layout,
|
|
598
|
+
...(prior.background !== undefined
|
|
599
|
+
? { background: prior.background }
|
|
600
|
+
: {}),
|
|
601
|
+
},
|
|
602
|
+
},
|
|
603
|
+
{
|
|
604
|
+
op: "reorder-slides",
|
|
605
|
+
orderedIds: before.slides.map((s) => s.id),
|
|
606
|
+
},
|
|
607
|
+
];
|
|
608
|
+
}
|
|
609
|
+
case "add-slide": {
|
|
610
|
+
// Inverse of adding a slide is deleting it.
|
|
611
|
+
return [
|
|
612
|
+
{
|
|
613
|
+
op: "delete-slide",
|
|
614
|
+
slideId: op.slideId,
|
|
615
|
+
...(before.slides.length === 0 ? { allowEmpty: true } : {}),
|
|
616
|
+
},
|
|
617
|
+
];
|
|
618
|
+
}
|
|
619
|
+
case "reorder-slides": {
|
|
620
|
+
// Inverse reorder = the order the slides were in before.
|
|
621
|
+
return [
|
|
622
|
+
{ op: "reorder-slides", orderedIds: before.slides.map((s) => s.id) },
|
|
623
|
+
];
|
|
624
|
+
}
|
|
625
|
+
case "patch-deck-fields": {
|
|
626
|
+
const priorFields: Record<string, unknown> = {};
|
|
627
|
+
const beforeRecord = before as unknown as Record<string, unknown>;
|
|
628
|
+
for (const key of Object.keys(op.fields)) {
|
|
629
|
+
priorFields[key] = beforeRecord[key];
|
|
630
|
+
}
|
|
631
|
+
return [
|
|
632
|
+
{
|
|
633
|
+
op: "patch-deck-fields",
|
|
634
|
+
fields: priorFields as PatchDeckFields,
|
|
635
|
+
},
|
|
636
|
+
];
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
349
641
|
/**
|
|
350
642
|
* Fetch the deck list. Returns `null` on any failure (network error, non-2xx
|
|
351
643
|
* response) so callers can distinguish "authoritative empty list" from
|
|
@@ -532,10 +824,14 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
532
824
|
const [loading, setLoading] = useState(true);
|
|
533
825
|
const decksRef = useRef<Deck[]>([]);
|
|
534
826
|
|
|
535
|
-
//
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
const
|
|
827
|
+
// Per-user inverse-op undo/redo. `canUndo`/`canRedo` are React state kept in
|
|
828
|
+
// sync with the controller via its onChange callback. The controller and its
|
|
829
|
+
// apply path are wired below once `decks`/enqueue are in scope.
|
|
830
|
+
const [canUndo, setCanUndo] = useState(false);
|
|
831
|
+
const [canRedo, setCanRedo] = useState(false);
|
|
832
|
+
const undoControllerRef = useRef<LocalOpUndoController<DeckUndoOp> | null>(
|
|
833
|
+
null,
|
|
834
|
+
);
|
|
539
835
|
// Track when external (SSE) updates happen so the save effect doesn't echo them back
|
|
540
836
|
const lastExternalUpdateRef = useRef(0);
|
|
541
837
|
// Track client-created decks that haven't been confirmed on the server yet.
|
|
@@ -553,28 +849,103 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
553
849
|
dirtyDeckIdsRef.current.add(deckId);
|
|
554
850
|
}, []);
|
|
555
851
|
|
|
556
|
-
const
|
|
557
|
-
const
|
|
558
|
-
if (
|
|
559
|
-
|
|
560
|
-
|
|
852
|
+
const deleteDeckAfterPendingCreate = useCallback((deckId: string) => {
|
|
853
|
+
const pendingCreate = pendingCreatePromisesRef.current.get(deckId);
|
|
854
|
+
if (!pendingCreate) {
|
|
855
|
+
void deleteDeckFromAPI(deckId);
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
void pendingCreate
|
|
860
|
+
.then(() => deleteDeckFromAPI(deckId))
|
|
861
|
+
.catch(() => {
|
|
862
|
+
// The create/duplicate failed, so there is nothing on the server to delete.
|
|
863
|
+
});
|
|
864
|
+
}, []);
|
|
865
|
+
|
|
866
|
+
// Plain local decks update. Undo entries are recorded explicitly by each
|
|
867
|
+
// mutation via `recordUndo` (inverse ops), so this no longer snapshots the
|
|
868
|
+
// whole decks array the way the old `setDecksWithHistory` did.
|
|
869
|
+
const setDecksLocal = useCallback((updater: (prev: Deck[]) => Deck[]) => {
|
|
870
|
+
setDecks(updater);
|
|
561
871
|
}, []);
|
|
562
872
|
|
|
563
873
|
useEffect(() => {
|
|
564
874
|
decksRef.current = decks;
|
|
565
875
|
}, [decks]);
|
|
566
876
|
|
|
877
|
+
// ── Inverse-op undo controller ────────────────────────────────────────────
|
|
878
|
+
// Applying an undo/redo entry runs each tagged op through the SAME optimistic
|
|
879
|
+
// local update + granular persist path as a normal edit. Because we only ever
|
|
880
|
+
// send granular ops (never full-replace), undo/redo can never clobber a
|
|
881
|
+
// concurrent edit to a different slide by another human or the agent. Entries
|
|
882
|
+
// that no longer apply (e.g. the slide was deleted remotely) fail soft:
|
|
883
|
+
// applyOpToDeck returns the deck unchanged and the granular server merge
|
|
884
|
+
// ignores the missing target.
|
|
885
|
+
if (!undoControllerRef.current) {
|
|
886
|
+
undoControllerRef.current = createLocalOpUndoController<DeckUndoOp>({
|
|
887
|
+
apply: (ops) => {
|
|
888
|
+
// Apply all ops to local state in one pass, then persist each.
|
|
889
|
+
setDecks((prev) => {
|
|
890
|
+
let next = prev;
|
|
891
|
+
for (const op of ops) {
|
|
892
|
+
next = applyUndoOpToDecks(next, op);
|
|
893
|
+
}
|
|
894
|
+
return next;
|
|
895
|
+
});
|
|
896
|
+
for (const op of ops) {
|
|
897
|
+
markDeckDirty(op.deckId);
|
|
898
|
+
if (op.op === "delete-deck") {
|
|
899
|
+
discardPendingDeckOps(op.deckId);
|
|
900
|
+
deleteDeckAfterPendingCreate(op.deckId);
|
|
901
|
+
} else if (op.op === "restore-deck" || op.op === "replace-deck") {
|
|
902
|
+
enqueueDeckOp(op.deckId, { op: "full-replace", deck: op.deck });
|
|
903
|
+
} else {
|
|
904
|
+
const { deckId, ...granular } = op;
|
|
905
|
+
enqueueDeckOp(deckId, granular);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
},
|
|
909
|
+
onChange: () => {
|
|
910
|
+
const c = undoControllerRef.current;
|
|
911
|
+
setCanUndo(c ? c.canUndo() : false);
|
|
912
|
+
setCanRedo(c ? c.canRedo() : false);
|
|
913
|
+
},
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
/**
|
|
918
|
+
* Record an undo entry for a just-applied local mutation. `before` is the
|
|
919
|
+
* deck state prior to the mutation (for inverse derivation); `redoOp` is the
|
|
920
|
+
* forward op that was applied. Same `coalesceKey` within the controller's
|
|
921
|
+
* window merges bursts (e.g. rapid text edits to one slide).
|
|
922
|
+
*/
|
|
923
|
+
const recordUndo = useCallback(
|
|
924
|
+
(
|
|
925
|
+
before: Deck,
|
|
926
|
+
redoOp: PatchDeckOp,
|
|
927
|
+
opts?: { label?: string; coalesceKey?: string },
|
|
928
|
+
) => {
|
|
929
|
+
const inverseOps = deriveInverseOp(before, redoOp);
|
|
930
|
+
if (!inverseOps || inverseOps.length === 0) return;
|
|
931
|
+
const entry: LocalOpUndoEntry<DeckUndoOp> = {
|
|
932
|
+
undo: inverseOps.map((o) => ({ deckId: before.id, ...o })),
|
|
933
|
+
redo: [{ deckId: before.id, ...redoOp }],
|
|
934
|
+
label: opts?.label,
|
|
935
|
+
coalesceKey: opts?.coalesceKey,
|
|
936
|
+
};
|
|
937
|
+
undoControllerRef.current?.push(entry);
|
|
938
|
+
},
|
|
939
|
+
[],
|
|
940
|
+
);
|
|
941
|
+
|
|
567
942
|
const resetDeckBaseline = useCallback((nextDecks: Deck[]) => {
|
|
568
|
-
skipHistoryRef.current = false;
|
|
569
943
|
setDecks(nextDecks);
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
},
|
|
576
|
-
]);
|
|
577
|
-
setHistoryIndex(0);
|
|
944
|
+
// A baseline reset (initial mount, route change, or access reload) starts a
|
|
945
|
+
// fresh undo timeline. Note: this is NOT the SSE/poll "remote update" path —
|
|
946
|
+
// those call setDecks directly and intentionally leave the undo stack
|
|
947
|
+
// intact so a collaborator's edit doesn't wipe your local undo history.
|
|
948
|
+
undoControllerRef.current?.clear();
|
|
578
949
|
}, []);
|
|
579
950
|
|
|
580
951
|
const reloadDecks = useCallback(async () => {
|
|
@@ -816,92 +1187,31 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
816
1187
|
return () => evtSource.close();
|
|
817
1188
|
}, []);
|
|
818
1189
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
});
|
|
837
|
-
setHistoryIndex((prev) => {
|
|
838
|
-
const truncatedLen = Math.min(prev + 1, history.length);
|
|
839
|
-
return Math.min(truncatedLen, MAX_HISTORY - 1);
|
|
840
|
-
});
|
|
841
|
-
},
|
|
842
|
-
[historyIndex, history.length],
|
|
843
|
-
);
|
|
844
|
-
|
|
845
|
-
const setDecksWithHistory = useCallback(
|
|
846
|
-
(label: string, updater: (prev: Deck[]) => Deck[]) => {
|
|
847
|
-
setDecks((prev) => {
|
|
848
|
-
const next = updater(prev);
|
|
849
|
-
// Push to history after state update
|
|
850
|
-
setTimeout(() => {
|
|
851
|
-
if (!skipHistoryRef.current) {
|
|
852
|
-
pushHistory(label, next);
|
|
853
|
-
}
|
|
854
|
-
skipHistoryRef.current = false;
|
|
855
|
-
}, 0);
|
|
856
|
-
return next;
|
|
857
|
-
});
|
|
858
|
-
},
|
|
859
|
-
[pushHistory],
|
|
860
|
-
);
|
|
1190
|
+
// Flush pending (debounced) saves before the tab is hidden or unloaded so the
|
|
1191
|
+
// last ~500ms of edits aren't lost on close/navigation. `pagehide` is the
|
|
1192
|
+
// reliable unload signal on modern browsers (incl. bfcache); we also flush on
|
|
1193
|
+
// `visibilitychange(hidden)` which fires on mobile tab-switch / app-background
|
|
1194
|
+
// where `pagehide` may not.
|
|
1195
|
+
useEffect(() => {
|
|
1196
|
+
const onHidden = () => {
|
|
1197
|
+
if (document.visibilityState === "hidden") flushPendingSaves();
|
|
1198
|
+
};
|
|
1199
|
+
const onPageHide = () => flushPendingSaves();
|
|
1200
|
+
document.addEventListener("visibilitychange", onHidden);
|
|
1201
|
+
window.addEventListener("pagehide", onPageHide);
|
|
1202
|
+
return () => {
|
|
1203
|
+
document.removeEventListener("visibilitychange", onHidden);
|
|
1204
|
+
window.removeEventListener("pagehide", onPageHide);
|
|
1205
|
+
};
|
|
1206
|
+
}, []);
|
|
861
1207
|
|
|
862
1208
|
const undo = useCallback(() => {
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
const nextDecks = JSON.parse(JSON.stringify(history[newIndex].decks));
|
|
866
|
-
// Undo restores a complete known-good snapshot — use full-replace so
|
|
867
|
-
// we don't try to infer granular ops from the diff.
|
|
868
|
-
for (const deck of nextDecks) {
|
|
869
|
-
enqueueDeckOp(deck.id, { op: "full-replace", deck });
|
|
870
|
-
}
|
|
871
|
-
markChangedDecksDirty(nextDecks);
|
|
872
|
-
setHistoryIndex(newIndex);
|
|
873
|
-
skipHistoryRef.current = true;
|
|
874
|
-
setDecks(nextDecks);
|
|
875
|
-
}, [historyIndex, history, markChangedDecksDirty]);
|
|
1209
|
+
void undoControllerRef.current?.undo();
|
|
1210
|
+
}, []);
|
|
876
1211
|
|
|
877
1212
|
const redo = useCallback(() => {
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
const nextDecks = JSON.parse(JSON.stringify(history[newIndex].decks));
|
|
881
|
-
// Same as undo — restore from a complete snapshot.
|
|
882
|
-
for (const deck of nextDecks) {
|
|
883
|
-
enqueueDeckOp(deck.id, { op: "full-replace", deck });
|
|
884
|
-
}
|
|
885
|
-
markChangedDecksDirty(nextDecks);
|
|
886
|
-
setHistoryIndex(newIndex);
|
|
887
|
-
skipHistoryRef.current = true;
|
|
888
|
-
setDecks(nextDecks);
|
|
889
|
-
}, [historyIndex, history, markChangedDecksDirty]);
|
|
890
|
-
|
|
891
|
-
const restoreFromHistory = useCallback(
|
|
892
|
-
(index: number) => {
|
|
893
|
-
if (index < 0 || index >= history.length) return;
|
|
894
|
-
const nextDecks = JSON.parse(JSON.stringify(history[index].decks));
|
|
895
|
-
for (const deck of nextDecks) {
|
|
896
|
-
enqueueDeckOp(deck.id, { op: "full-replace", deck });
|
|
897
|
-
}
|
|
898
|
-
markChangedDecksDirty(nextDecks);
|
|
899
|
-
setHistoryIndex(index);
|
|
900
|
-
skipHistoryRef.current = true;
|
|
901
|
-
setDecks(nextDecks);
|
|
902
|
-
},
|
|
903
|
-
[history, markChangedDecksDirty],
|
|
904
|
-
);
|
|
1213
|
+
void undoControllerRef.current?.redo();
|
|
1214
|
+
}, []);
|
|
905
1215
|
|
|
906
1216
|
// Keyboard shortcuts for undo/redo
|
|
907
1217
|
useEffect(() => {
|
|
@@ -937,6 +1247,7 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
937
1247
|
title?: string,
|
|
938
1248
|
options?: { noDefaultSlides?: boolean; designSystemId?: string | null },
|
|
939
1249
|
): Deck => {
|
|
1250
|
+
const insertIndex = decksRef.current.length;
|
|
940
1251
|
const newDeck: Deck = {
|
|
941
1252
|
id: nanoid(10),
|
|
942
1253
|
title: title || "Untitled Deck",
|
|
@@ -980,10 +1291,22 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
980
1291
|
pendingCreatePromisesRef.current.delete(newDeck.id);
|
|
981
1292
|
}
|
|
982
1293
|
});
|
|
983
|
-
|
|
1294
|
+
setDecksLocal((prev) => [...prev, newDeck]);
|
|
1295
|
+
undoControllerRef.current?.push({
|
|
1296
|
+
undo: [{ op: "delete-deck", deckId: newDeck.id }],
|
|
1297
|
+
redo: [
|
|
1298
|
+
{
|
|
1299
|
+
op: "restore-deck",
|
|
1300
|
+
deckId: newDeck.id,
|
|
1301
|
+
deck: newDeck,
|
|
1302
|
+
index: insertIndex,
|
|
1303
|
+
},
|
|
1304
|
+
],
|
|
1305
|
+
label: "Create deck",
|
|
1306
|
+
});
|
|
984
1307
|
return newDeck;
|
|
985
1308
|
},
|
|
986
|
-
[
|
|
1309
|
+
[setDecksLocal],
|
|
987
1310
|
);
|
|
988
1311
|
|
|
989
1312
|
const ensureDeckPersisted = useCallback(async (id: string) => {
|
|
@@ -1008,6 +1331,7 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
1008
1331
|
|
|
1009
1332
|
const now = new Date().toISOString();
|
|
1010
1333
|
const newTitle = title || `Copy of ${source.title}`;
|
|
1334
|
+
const insertIndex = decksRef.current.length;
|
|
1011
1335
|
// Re-id slides so optimistic edits to the copy don't collide with the
|
|
1012
1336
|
// original. The server does the same thing — these client ids will be
|
|
1013
1337
|
// replaced by server-generated ones once the duplicate action lands and
|
|
@@ -1035,11 +1359,16 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
1035
1359
|
pendingDuplicateSourceIdsRef.current.add(sourceDeckId);
|
|
1036
1360
|
|
|
1037
1361
|
// Fire the action in the background. On error, roll back.
|
|
1038
|
-
callAction<DuplicateDeckActionResult>(
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1362
|
+
const duplicatePromise = callAction<DuplicateDeckActionResult>(
|
|
1363
|
+
"duplicate-deck",
|
|
1364
|
+
{
|
|
1365
|
+
deckId: sourceDeckId,
|
|
1366
|
+
newId,
|
|
1367
|
+
title,
|
|
1368
|
+
},
|
|
1369
|
+
).then(() => undefined);
|
|
1370
|
+
pendingCreatePromisesRef.current.set(newId, duplicatePromise);
|
|
1371
|
+
duplicatePromise
|
|
1043
1372
|
.catch((err) => {
|
|
1044
1373
|
console.error("Duplicate failed:", err);
|
|
1045
1374
|
// Roll back: drop the optimistic deck from local state.
|
|
@@ -1047,23 +1376,55 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
1047
1376
|
})
|
|
1048
1377
|
.finally(() => {
|
|
1049
1378
|
pendingCreateIdsRef.current.delete(newId);
|
|
1379
|
+
if (
|
|
1380
|
+
pendingCreatePromisesRef.current.get(newId) === duplicatePromise
|
|
1381
|
+
) {
|
|
1382
|
+
pendingCreatePromisesRef.current.delete(newId);
|
|
1383
|
+
}
|
|
1050
1384
|
pendingDuplicateSourceIdsRef.current.delete(sourceDeckId);
|
|
1051
1385
|
});
|
|
1052
1386
|
|
|
1053
|
-
|
|
1387
|
+
setDecksLocal((prev) => [...prev, optimistic]);
|
|
1388
|
+
undoControllerRef.current?.push({
|
|
1389
|
+
undo: [{ op: "delete-deck", deckId: optimistic.id }],
|
|
1390
|
+
redo: [
|
|
1391
|
+
{
|
|
1392
|
+
op: "restore-deck",
|
|
1393
|
+
deckId: optimistic.id,
|
|
1394
|
+
deck: optimistic,
|
|
1395
|
+
index: insertIndex,
|
|
1396
|
+
},
|
|
1397
|
+
],
|
|
1398
|
+
label: "Duplicate deck",
|
|
1399
|
+
});
|
|
1054
1400
|
return optimistic;
|
|
1055
1401
|
},
|
|
1056
|
-
[decks,
|
|
1402
|
+
[decks, setDecksLocal],
|
|
1057
1403
|
);
|
|
1058
1404
|
|
|
1059
1405
|
const deleteDeck = useCallback(
|
|
1060
1406
|
(id: string) => {
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
);
|
|
1407
|
+
const beforeDeck = decksRef.current.find((deck) => deck.id === id);
|
|
1408
|
+
const beforeIndex = decksRef.current.findIndex((deck) => deck.id === id);
|
|
1409
|
+
discardPendingDeckOps(id);
|
|
1410
|
+
deleteDeckAfterPendingCreate(id);
|
|
1411
|
+
setDecksLocal((prev) => prev.filter((d) => d.id !== id));
|
|
1412
|
+
if (beforeDeck) {
|
|
1413
|
+
undoControllerRef.current?.push({
|
|
1414
|
+
undo: [
|
|
1415
|
+
{
|
|
1416
|
+
op: "restore-deck",
|
|
1417
|
+
deckId: id,
|
|
1418
|
+
deck: beforeDeck,
|
|
1419
|
+
index: beforeIndex,
|
|
1420
|
+
},
|
|
1421
|
+
],
|
|
1422
|
+
redo: [{ op: "delete-deck", deckId: id }],
|
|
1423
|
+
label: "Delete deck",
|
|
1424
|
+
});
|
|
1425
|
+
}
|
|
1065
1426
|
},
|
|
1066
|
-
[
|
|
1427
|
+
[deleteDeckAfterPendingCreate, setDecksLocal],
|
|
1067
1428
|
);
|
|
1068
1429
|
|
|
1069
1430
|
const updateDeck = useCallback(
|
|
@@ -1071,6 +1432,7 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
1071
1432
|
// Clear the external-update suppression window so a rename/update that
|
|
1072
1433
|
// happens within 2s of page load (or an SSE event) is not silently dropped.
|
|
1073
1434
|
markDeckDirty(id);
|
|
1435
|
+
const before = decksRef.current.find((d) => d.id === id);
|
|
1074
1436
|
setDecks((prev) =>
|
|
1075
1437
|
prev.map((d) =>
|
|
1076
1438
|
d.id === id
|
|
@@ -1093,13 +1455,24 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
1093
1455
|
void _slides;
|
|
1094
1456
|
_ca;
|
|
1095
1457
|
if (Object.keys(persistableUpdates).length > 0) {
|
|
1096
|
-
|
|
1458
|
+
const op: PatchDeckOp = {
|
|
1097
1459
|
op: "patch-deck-fields",
|
|
1098
|
-
fields: persistableUpdates,
|
|
1099
|
-
}
|
|
1460
|
+
fields: persistableUpdates as PatchDeckFields,
|
|
1461
|
+
};
|
|
1462
|
+
enqueueDeckOp(id, op);
|
|
1463
|
+
if (before) {
|
|
1464
|
+
// Coalesce rapid deck-field edits (e.g. title typing, tweak sliders)
|
|
1465
|
+
// per field-set so a burst becomes one undo step.
|
|
1466
|
+
recordUndo(before, op, {
|
|
1467
|
+
label: "Update deck",
|
|
1468
|
+
coalesceKey: `${id}:deck-fields:${Object.keys(persistableUpdates)
|
|
1469
|
+
.sort()
|
|
1470
|
+
.join(",")}`,
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1100
1473
|
}
|
|
1101
1474
|
},
|
|
1102
|
-
[markDeckDirty],
|
|
1475
|
+
[markDeckDirty, recordUndo],
|
|
1103
1476
|
);
|
|
1104
1477
|
|
|
1105
1478
|
const getDeck = useCallback(
|
|
@@ -1118,8 +1491,9 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
1118
1491
|
background: "bg-[#000000]",
|
|
1119
1492
|
};
|
|
1120
1493
|
|
|
1494
|
+
const before = decksRef.current.find((d) => d.id === deckId);
|
|
1121
1495
|
let afterSlideId: string | undefined;
|
|
1122
|
-
|
|
1496
|
+
setDecksLocal((prev) =>
|
|
1123
1497
|
prev.map((d) => {
|
|
1124
1498
|
if (d.id !== deckId) return d;
|
|
1125
1499
|
const slides = [...d.slides];
|
|
@@ -1134,7 +1508,7 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
1134
1508
|
|
|
1135
1509
|
// Granular op — the server splices in only this slide, preserving any
|
|
1136
1510
|
// concurrent changes to other slides.
|
|
1137
|
-
|
|
1511
|
+
const op: PatchDeckOp = {
|
|
1138
1512
|
op: "add-slide",
|
|
1139
1513
|
slideId: newSlide.id,
|
|
1140
1514
|
afterSlideId,
|
|
@@ -1144,11 +1518,13 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
1144
1518
|
layout: newSlide.layout,
|
|
1145
1519
|
background: newSlide.background,
|
|
1146
1520
|
},
|
|
1147
|
-
}
|
|
1521
|
+
};
|
|
1522
|
+
enqueueDeckOp(deckId, op);
|
|
1523
|
+
if (before) recordUndo(before, op, { label: "Add slide" });
|
|
1148
1524
|
|
|
1149
1525
|
return newSlide.id;
|
|
1150
1526
|
},
|
|
1151
|
-
[markDeckDirty,
|
|
1527
|
+
[markDeckDirty, recordUndo, setDecksLocal],
|
|
1152
1528
|
);
|
|
1153
1529
|
|
|
1154
1530
|
const updateSlide = useCallback(
|
|
@@ -1161,7 +1537,8 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
1161
1537
|
: updates.content
|
|
1162
1538
|
? "Update content"
|
|
1163
1539
|
: "Edit slide";
|
|
1164
|
-
|
|
1540
|
+
const before = decksRef.current.find((d) => d.id === deckId);
|
|
1541
|
+
setDecksLocal((prev: Deck[]) =>
|
|
1165
1542
|
prev.map((d) => {
|
|
1166
1543
|
if (d.id !== deckId) return d;
|
|
1167
1544
|
return {
|
|
@@ -1174,15 +1551,28 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
1174
1551
|
}),
|
|
1175
1552
|
);
|
|
1176
1553
|
// Granular op — only this slide's changed fields reach the server.
|
|
1177
|
-
|
|
1554
|
+
const op: PatchDeckOp = { op: "patch-slide", slideId, fields: updates };
|
|
1555
|
+
enqueueDeckOp(deckId, op);
|
|
1556
|
+
if (before) {
|
|
1557
|
+
// Coalesce a burst of edits to the SAME slide's SAME field-set into one
|
|
1558
|
+
// undo step (e.g. typing characters into inline text). Distinct
|
|
1559
|
+
// field-sets (content vs background vs layout) get distinct undo steps.
|
|
1560
|
+
recordUndo(before, op, {
|
|
1561
|
+
label,
|
|
1562
|
+
coalesceKey: `${deckId}:${slideId}:${Object.keys(updates)
|
|
1563
|
+
.sort()
|
|
1564
|
+
.join(",")}`,
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1178
1567
|
},
|
|
1179
|
-
[markDeckDirty,
|
|
1568
|
+
[markDeckDirty, recordUndo, setDecksLocal],
|
|
1180
1569
|
);
|
|
1181
1570
|
|
|
1182
1571
|
const deleteSlide = useCallback(
|
|
1183
1572
|
(deckId: string, slideId: string) => {
|
|
1184
1573
|
markDeckDirty(deckId);
|
|
1185
|
-
|
|
1574
|
+
const before = decksRef.current.find((d) => d.id === deckId);
|
|
1575
|
+
setDecksLocal((prev) =>
|
|
1186
1576
|
prev.map((d) => {
|
|
1187
1577
|
if (d.id !== deckId) return d;
|
|
1188
1578
|
const slides = d.slides.filter((s) => s.id !== slideId);
|
|
@@ -1198,16 +1588,22 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
1198
1588
|
}),
|
|
1199
1589
|
);
|
|
1200
1590
|
// Granular op — server deletes only this slide from the blob.
|
|
1201
|
-
|
|
1591
|
+
const op: PatchDeckOp = { op: "delete-slide", slideId };
|
|
1592
|
+
enqueueDeckOp(deckId, op);
|
|
1593
|
+
// Inverse re-adds the full prior slide at its old position, so undo
|
|
1594
|
+
// restores content/notes/layout/background exactly. (This is the case
|
|
1595
|
+
// behind the "Undo delete" toast in DeckEditor.)
|
|
1596
|
+
if (before) recordUndo(before, op, { label: "Delete slide" });
|
|
1202
1597
|
},
|
|
1203
|
-
[markDeckDirty,
|
|
1598
|
+
[markDeckDirty, recordUndo, setDecksLocal],
|
|
1204
1599
|
);
|
|
1205
1600
|
|
|
1206
1601
|
const duplicateSlide = useCallback(
|
|
1207
1602
|
(deckId: string, slideId: string) => {
|
|
1208
1603
|
markDeckDirty(deckId);
|
|
1604
|
+
const before = decksRef.current.find((d) => d.id === deckId);
|
|
1209
1605
|
let copiedSlide: Slide | undefined;
|
|
1210
|
-
|
|
1606
|
+
setDecksLocal((prev) =>
|
|
1211
1607
|
prev.map((d) => {
|
|
1212
1608
|
if (d.id !== deckId) return d;
|
|
1213
1609
|
const idx = d.slides.findIndex((s) => s.id === slideId);
|
|
@@ -1223,7 +1619,7 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
1223
1619
|
if (copiedSlide) {
|
|
1224
1620
|
// Granular add-slide op — inserts the copy after the original.
|
|
1225
1621
|
const { id: newSlideId, ...rest } = copiedSlide;
|
|
1226
|
-
|
|
1622
|
+
const op: PatchDeckOp = {
|
|
1227
1623
|
op: "add-slide",
|
|
1228
1624
|
slideId: newSlideId,
|
|
1229
1625
|
afterSlideId: slideId,
|
|
@@ -1233,17 +1629,20 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
1233
1629
|
layout: rest.layout,
|
|
1234
1630
|
background: rest.background,
|
|
1235
1631
|
},
|
|
1236
|
-
}
|
|
1632
|
+
};
|
|
1633
|
+
enqueueDeckOp(deckId, op);
|
|
1634
|
+
if (before) recordUndo(before, op, { label: "Duplicate slide" });
|
|
1237
1635
|
}
|
|
1238
1636
|
},
|
|
1239
|
-
[markDeckDirty,
|
|
1637
|
+
[markDeckDirty, recordUndo, setDecksLocal],
|
|
1240
1638
|
);
|
|
1241
1639
|
|
|
1242
1640
|
const reorderSlides = useCallback(
|
|
1243
1641
|
(deckId: string, oldIndex: number, newIndex: number) => {
|
|
1244
1642
|
markDeckDirty(deckId);
|
|
1643
|
+
const before = decksRef.current.find((d) => d.id === deckId);
|
|
1245
1644
|
let orderedIds: string[] | undefined;
|
|
1246
|
-
|
|
1645
|
+
setDecksLocal((prev) =>
|
|
1247
1646
|
prev.map((d) => {
|
|
1248
1647
|
if (d.id !== deckId) return d;
|
|
1249
1648
|
const slides = [...d.slides];
|
|
@@ -1256,28 +1655,45 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
1256
1655
|
if (orderedIds) {
|
|
1257
1656
|
// Granular op — server reorders by slide ID rather than by index,
|
|
1258
1657
|
// so concurrent adds from other writers don't get dropped.
|
|
1259
|
-
|
|
1658
|
+
const op: PatchDeckOp = { op: "reorder-slides", orderedIds };
|
|
1659
|
+
enqueueDeckOp(deckId, op);
|
|
1660
|
+
if (before) recordUndo(before, op, { label: "Reorder slides" });
|
|
1260
1661
|
}
|
|
1261
1662
|
},
|
|
1262
|
-
[markDeckDirty,
|
|
1663
|
+
[markDeckDirty, recordUndo, setDecksLocal],
|
|
1263
1664
|
);
|
|
1264
1665
|
|
|
1265
1666
|
const setDeckSlides = useCallback(
|
|
1266
1667
|
(deckId: string, slides: Slide[]) => {
|
|
1267
1668
|
markDeckDirty(deckId);
|
|
1268
|
-
|
|
1669
|
+
const before = decksRef.current.find((deck) => deck.id === deckId);
|
|
1670
|
+
const after = before
|
|
1671
|
+
? { ...before, slides, updatedAt: new Date().toISOString() }
|
|
1672
|
+
: null;
|
|
1673
|
+
// setDeckSlides replaces ALL slides wholesale (used by AI generation and
|
|
1674
|
+
// imports), so its undo entry is a deck-level full replacement instead of
|
|
1675
|
+
// a fine-grained slide patch.
|
|
1676
|
+
setDecksLocal((prev) =>
|
|
1269
1677
|
prev.map((d) => {
|
|
1270
1678
|
if (d.id !== deckId) return d;
|
|
1271
|
-
const
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1679
|
+
const next = after ?? {
|
|
1680
|
+
...d,
|
|
1681
|
+
slides,
|
|
1682
|
+
updatedAt: new Date().toISOString(),
|
|
1683
|
+
};
|
|
1684
|
+
enqueueDeckOp(deckId, { op: "full-replace", deck: next });
|
|
1685
|
+
return next;
|
|
1277
1686
|
}),
|
|
1278
1687
|
);
|
|
1688
|
+
if (before && after) {
|
|
1689
|
+
undoControllerRef.current?.push({
|
|
1690
|
+
undo: [{ op: "replace-deck", deckId, deck: before }],
|
|
1691
|
+
redo: [{ op: "replace-deck", deckId, deck: after }],
|
|
1692
|
+
label: "Replace slides",
|
|
1693
|
+
});
|
|
1694
|
+
}
|
|
1279
1695
|
},
|
|
1280
|
-
[markDeckDirty,
|
|
1696
|
+
[markDeckDirty, setDecksLocal],
|
|
1281
1697
|
);
|
|
1282
1698
|
|
|
1283
1699
|
return (
|
|
@@ -1301,11 +1717,8 @@ export function DeckProvider({ children }: { children: ReactNode }) {
|
|
|
1301
1717
|
markDeckDirty,
|
|
1302
1718
|
undo,
|
|
1303
1719
|
redo,
|
|
1304
|
-
canUndo
|
|
1305
|
-
canRedo
|
|
1306
|
-
history,
|
|
1307
|
-
historyIndex,
|
|
1308
|
-
restoreFromHistory,
|
|
1720
|
+
canUndo,
|
|
1721
|
+
canRedo,
|
|
1309
1722
|
}}
|
|
1310
1723
|
>
|
|
1311
1724
|
{children}
|