@agent-native/core 0.163.3 → 0.164.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +29 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +1 -2
- package/corpus/templates/analytics/netlify.toml +2 -0
- package/corpus/templates/assets/netlify.toml +2 -0
- package/corpus/templates/brain/netlify.toml +2 -0
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +2 -1
- package/corpus/templates/calendar/changelog/2026-08-19-calendar-page-loads-stay-fast-after-idle-periods.md +6 -0
- package/corpus/templates/calendar/netlify.toml +2 -0
- package/corpus/templates/chat/netlify.toml +2 -0
- package/corpus/templates/clips/app/components/player/player-controls.tsx +3 -3
- package/corpus/templates/clips/app/components/player/reaction-emojis.ts +12 -0
- package/corpus/templates/clips/app/components/player/reactions-tray.tsx +90 -9
- package/corpus/templates/clips/app/components/player/scrubber.tsx +43 -10
- package/corpus/templates/clips/app/components/player/video-player.tsx +105 -15
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +1 -2
- package/corpus/templates/clips/app/routes/_index.tsx +10 -2
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +13 -8
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +15 -8
- package/corpus/templates/clips/netlify.toml +2 -0
- package/corpus/templates/clips/server/routes/[...page].head.ts +5 -0
- package/corpus/templates/content/netlify.toml +2 -0
- package/corpus/templates/design/.agents/skills/design-editor-architecture/SKILL.md +103 -0
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +2 -1
- package/corpus/templates/design/AGENTS.md +3 -1
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +3 -2
- package/corpus/templates/design/app/components/design/editor/AgentNativeMenuMark.tsx +25 -0
- package/corpus/templates/design/app/components/design/editor/DesignBottomToolbar.tsx +411 -0
- package/corpus/templates/design/app/components/design/editor/DesignCollaborators.tsx +153 -0
- package/corpus/templates/design/app/components/design/editor/DesignWorkspaceRail.tsx +231 -0
- package/corpus/templates/design/app/components/design/editor/MakeRealDialog.tsx +223 -0
- package/corpus/templates/design/app/components/design/editor/PendingScreenDeletionDialog.tsx +58 -0
- package/corpus/templates/design/app/components/design/editor/PendingVisualStyleWarningDialog.tsx +56 -0
- package/corpus/templates/design/app/components/design/editor/ReadOnlyEditorPanel.tsx +23 -0
- package/corpus/templates/design/app/components/design/editor/SaveTemplateDialog.tsx +161 -0
- package/corpus/templates/design/app/components/design/editor/toolbar-controls.tsx +185 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +20913 -0
- package/corpus/templates/design/app/pages/design-editor/command-types.ts +120 -0
- package/corpus/templates/design/app/pages/design-editor/commands/README.md +193 -0
- package/corpus/templates/design/app/pages/design-editor/commands/add-auto-layout.ts +416 -0
- package/corpus/templates/design/app/pages/design-editor/commands/add-screen.ts +112 -0
- package/corpus/templates/design/app/pages/design-editor/commands/align-selection.ts +183 -0
- package/corpus/templates/design/app/pages/design-editor/commands/apply-design-editor-command.ts +243 -0
- package/corpus/templates/design/app/pages/design-editor/commands/apply-file-content-update.ts +236 -0
- package/corpus/templates/design/app/pages/design-editor/commands/apply-local-content-update.ts +304 -0
- package/corpus/templates/design/app/pages/design-editor/commands/apply-pending-visual-styles-with-agent.ts +376 -0
- package/corpus/templates/design/app/pages/design-editor/commands/apply-to-source.ts +132 -0
- package/corpus/templates/design/app/pages/design-editor/commands/can-move-layer.ts +117 -0
- package/corpus/templates/design/app/pages/design-editor/commands/change-selected-z-index.ts +212 -0
- package/corpus/templates/design/app/pages/design-editor/commands/commit-relative-style-delta-to-selected-layers.ts +176 -0
- package/corpus/templates/design/app/pages/design-editor/commands/commit-styles-to-selected-layers.ts +175 -0
- package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +710 -0
- package/corpus/templates/design/app/pages/design-editor/commands/confirm-make-real.ts +99 -0
- package/corpus/templates/design/app/pages/design-editor/commands/copy-as-figma-svg.ts +95 -0
- package/corpus/templates/design/app/pages/design-editor/commands/copy-selection.ts +156 -0
- package/corpus/templates/design/app/pages/design-editor/commands/create-primitive.ts +291 -0
- package/corpus/templates/design/app/pages/design-editor/commands/create-screen-frame.ts +131 -0
- package/corpus/templates/design/app/pages/design-editor/commands/cross-screen-element-drop.ts +577 -0
- package/corpus/templates/design/app/pages/design-editor/commands/delete-files.ts +364 -0
- package/corpus/templates/design/app/pages/design-editor/commands/delete-selection.ts +521 -0
- package/corpus/templates/design/app/pages/design-editor/commands/detach-instance-menu-action.ts +88 -0
- package/corpus/templates/design/app/pages/design-editor/commands/distribute-selection.ts +114 -0
- package/corpus/templates/design/app/pages/design-editor/commands/download-all-screens-pdf.ts +116 -0
- package/corpus/templates/design/app/pages/design-editor/commands/download-pdf.ts +103 -0
- package/corpus/templates/design/app/pages/design-editor/commands/download-svg.ts +159 -0
- package/corpus/templates/design/app/pages/design-editor/commands/duplicate-screen.ts +201 -0
- package/corpus/templates/design/app/pages/design-editor/commands/duplicate-selection.ts +279 -0
- package/corpus/templates/design/app/pages/design-editor/commands/editor-paste.ts +81 -0
- package/corpus/templates/design/app/pages/design-editor/commands/enter-hotkey.ts +155 -0
- package/corpus/templates/design/app/pages/design-editor/commands/enter-single-screen.ts +168 -0
- package/corpus/templates/design/app/pages/design-editor/commands/escape-hotkey.ts +212 -0
- package/corpus/templates/design/app/pages/design-editor/commands/frame-selection.ts +119 -0
- package/corpus/templates/design/app/pages/design-editor/commands/geometry-commit.ts +169 -0
- package/corpus/templates/design/app/pages/design-editor/commands/get-selected-layer-snapshots.ts +177 -0
- package/corpus/templates/design/app/pages/design-editor/commands/group-selection.ts +127 -0
- package/corpus/templates/design/app/pages/design-editor/commands/iframe-context-menu.ts +121 -0
- package/corpus/templates/design/app/pages/design-editor/commands/import-figma-clipboard-into-design.ts +119 -0
- package/corpus/templates/design/app/pages/design-editor/commands/layer-marquee-selection-change.ts +163 -0
- package/corpus/templates/design/app/pages/design-editor/commands/layer-move-to-screen.ts +331 -0
- package/corpus/templates/design/app/pages/design-editor/commands/layer-move.ts +517 -0
- package/corpus/templates/design/app/pages/design-editor/commands/layer-rename.ts +247 -0
- package/corpus/templates/design/app/pages/design-editor/commands/layer-selection-change.ts +161 -0
- package/corpus/templates/design/app/pages/design-editor/commands/mode-change.ts +122 -0
- package/corpus/templates/design/app/pages/design-editor/commands/nudge-selection.ts +213 -0
- package/corpus/templates/design/app/pages/design-editor/commands/overview-primitive-reparent.ts +318 -0
- package/corpus/templates/design/app/pages/design-editor/commands/paste-copied-screens.ts +114 -0
- package/corpus/templates/design/app/pages/design-editor/commands/paste-over-selection.ts +70 -0
- package/corpus/templates/design/app/pages/design-editor/commands/paste-selection.ts +436 -0
- package/corpus/templates/design/app/pages/design-editor/commands/paste-to-replace.ts +135 -0
- package/corpus/templates/design/app/pages/design-editor/commands/pasted-image-files.ts +209 -0
- package/corpus/templates/design/app/pages/design-editor/commands/persist-frame-geometry-save.ts +113 -0
- package/corpus/templates/design/app/pages/design-editor/commands/persist-tweak-save.ts +160 -0
- package/corpus/templates/design/app/pages/design-editor/commands/primitive-created.ts +168 -0
- package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-layer-state-edit.ts +149 -0
- package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-structure-edit.ts +200 -0
- package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-text-edit.ts +152 -0
- package/corpus/templates/design/app/pages/design-editor/commands/record-pending-visual-style-edit.ts +247 -0
- package/corpus/templates/design/app/pages/design-editor/commands/redo.ts +1086 -0
- package/corpus/templates/design/app/pages/design-editor/commands/render-png-blob.ts +200 -0
- package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +207 -0
- package/corpus/templates/design/app/pages/design-editor/commands/screen-element-select.ts +262 -0
- package/corpus/templates/design/app/pages/design-editor/commands/screen-text-content-change.ts +216 -0
- package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-duplicate-change.ts +108 -0
- package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-structure-change.ts +280 -0
- package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-style-change.ts +151 -0
- package/corpus/templates/design/app/pages/design-editor/commands/send-overview-annotations.ts +88 -0
- package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-move-semantic-handoff.ts +122 -0
- package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-semantic-handoff.ts +143 -0
- package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-state-semantic-handoff.ts +115 -0
- package/corpus/templates/design/app/pages/design-editor/commands/start-retry-generation.ts +152 -0
- package/corpus/templates/design/app/pages/design-editor/commands/start-sidebar-resize.ts +99 -0
- package/corpus/templates/design/app/pages/design-editor/commands/style-change.ts +152 -0
- package/corpus/templates/design/app/pages/design-editor/commands/styles-change.ts +155 -0
- package/corpus/templates/design/app/pages/design-editor/commands/suggest-auto-layout.ts +125 -0
- package/corpus/templates/design/app/pages/design-editor/commands/text-content-change.ts +201 -0
- package/corpus/templates/design/app/pages/design-editor/commands/tidy-up.ts +123 -0
- package/corpus/templates/design/app/pages/design-editor/commands/toggle-layer-hidden.ts +167 -0
- package/corpus/templates/design/app/pages/design-editor/commands/toggle-layer-locked.ts +175 -0
- package/corpus/templates/design/app/pages/design-editor/commands/toggle-motion-keyframe.ts +99 -0
- package/corpus/templates/design/app/pages/design-editor/commands/tweak-prompt-submit.ts +84 -0
- package/corpus/templates/design/app/pages/design-editor/commands/undo.ts +906 -0
- package/corpus/templates/design/app/pages/design-editor/commands/ungroup-selection.ts +170 -0
- package/corpus/templates/design/app/pages/design-editor/commands/visual-duplicate-change.ts +119 -0
- package/corpus/templates/design/app/pages/design-editor/commands/visual-structure-change.ts +274 -0
- package/corpus/templates/design/app/pages/design-editor/commands/write-frame-geometry-snapshot.ts +99 -0
- package/corpus/templates/design/app/pages/design-editor/derive/design-breakpoints.ts +56 -0
- package/corpus/templates/design/app/pages/design-editor/derive/overview-screens.ts +146 -0
- package/corpus/templates/design/app/pages/design-editor/domains/use-tweaks.ts +326 -0
- package/corpus/templates/design/app/pages/design-editor/editor-constants.ts +54 -0
- package/corpus/templates/design/app/pages/design-editor/editor-helpers.ts +183 -0
- package/corpus/templates/design/app/pages/design-editor/effects/adopt-db-file-content.ts +270 -0
- package/corpus/templates/design/app/pages/design-editor/effects/mirror-selection-to-agent-chat.ts +134 -0
- package/corpus/templates/design/app/pages/design-editor/effects/motion-autosave.ts +335 -0
- package/corpus/templates/design/app/pages/design-editor/effects/observe-collab-text.ts +183 -0
- package/corpus/templates/design/app/pages/design-editor/effects/publish-agent-selection-context.ts +252 -0
- package/corpus/templates/design/app/pages/design-editor/effects/resume-pending-generation.ts +178 -0
- package/corpus/templates/design/app/pages/design-editor/effects/seed-collab-content.ts +157 -0
- package/corpus/templates/design/app/pages/design-editor/preview-html.ts +19 -0
- package/corpus/templates/design/netlify.toml +2 -0
- package/corpus/templates/design/shared/board-file.ts +2 -1
- package/corpus/templates/dispatch/netlify.toml +2 -0
- package/corpus/templates/forms/netlify.toml +2 -0
- package/corpus/templates/mail/netlify.toml +2 -0
- package/corpus/templates/plan/netlify.toml +2 -0
- package/corpus/templates/slides/netlify.toml +2 -0
- package/dist/agent/engine/builder-engine.d.ts +6 -4
- package/dist/agent/engine/builder-engine.js +91 -26
- package/dist/agent/engine/registry.js +16 -0
- package/dist/cli/code-agent-agent-tools.d.ts +2 -0
- package/dist/cli/code-agent-agent-tools.js +188 -0
- package/dist/cli/code-agent-collaboration.d.ts +34 -0
- package/dist/cli/code-agent-collaboration.js +104 -0
- package/dist/cli/code-agent-executor.js +6 -0
- package/dist/cli/code-agent-schedules.d.ts +54 -0
- package/dist/cli/code-agent-schedules.js +198 -0
- package/dist/client/AgentPanel.js +1 -1
- package/dist/client/ConnectBuilderCard.js +5 -2
- package/dist/client/EnvironmentBadge.js +6 -5
- package/dist/client/chat/run-recovery.js +3 -2
- package/dist/client/chat/tool-call-display.js +2 -3
- package/dist/client/components/CodeRequiredDialog.js +4 -2
- package/dist/client/i18n.js +5 -4
- package/dist/client/settings/SettingsPanel.js +1 -1
- package/dist/client/settings/VoiceTranscriptionSection.js +1 -1
- package/dist/client/settings/useBuilderStatus.d.ts +6 -1
- package/dist/client/settings/useBuilderStatus.js +22 -33
- package/dist/client/transcription/BuilderTranscriptionCta.js +1 -1
- package/dist/code-agents/index.d.ts +2 -0
- package/dist/code-agents/index.js +2 -0
- package/dist/collab/routes.d.ts +1 -1
- package/dist/localization/shared.d.ts +2 -0
- package/dist/localization/shared.js +5 -0
- package/dist/mcp-client/oauth-client.d.ts +1 -0
- package/dist/mcp-client/oauth-client.js +50 -1
- package/dist/observability/routes.d.ts +1 -1
- package/dist/provider-api/actions/provider-api.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/auth-marketing-locales.d.ts +7 -0
- package/dist/server/auth-marketing-locales.js +1322 -0
- package/dist/server/auth-marketing.d.ts +1 -0
- package/dist/server/auth-marketing.js +35 -6
- package/dist/server/auth.js +4 -17
- package/dist/server/builder-browser.d.ts +22 -0
- package/dist/server/builder-browser.js +77 -1
- package/dist/server/builder-oauth.d.ts +50 -0
- package/dist/server/builder-oauth.js +263 -0
- package/dist/server/core-routes-plugin.d.ts +11 -0
- package/dist/server/core-routes-plugin.js +291 -415
- package/dist/server/credential-provider.d.ts +1 -1
- package/dist/server/credential-provider.js +1 -1
- package/dist/server/csrf.js +2 -2
- package/dist/server/onboarding-html.js +52 -120
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/settings/store.d.ts +9 -0
- package/dist/settings/store.js +19 -0
- package/dist/templates/chat/netlify.toml +2 -0
- package/dist/vite/client.js +35 -37
- package/docs/content/deployment.mdx +2 -0
- package/docs/content/multi-app-workspace.mdx +1 -1
- package/package.json +2 -2
- package/src/templates/chat/netlify.toml +2 -0
package/corpus/README.md
CHANGED
|
@@ -132,6 +132,7 @@ export function useDashboardChatContext(
|
|
|
132
132
|
: null;
|
|
133
133
|
const hasSelectedPanel = selectedPanelId !== null;
|
|
134
134
|
const publishedSelectionRef = useRef<SelectedDashboardObject | null>(null);
|
|
135
|
+
const suppressDashboardContextRef = useRef(false);
|
|
135
136
|
|
|
136
137
|
const selectPanelForChat = useCallback(
|
|
137
138
|
(
|
|
@@ -140,6 +141,7 @@ export function useDashboardChatContext(
|
|
|
140
141
|
) => {
|
|
141
142
|
if (!id) return;
|
|
142
143
|
if (options.openSidebar !== true && !isAgentSidebarOpen) return;
|
|
144
|
+
suppressDashboardContextRef.current = false;
|
|
143
145
|
const displayDashboardTitle = title?.trim() || id;
|
|
144
146
|
const displayPanelTitle = panel.panelTitle.trim() || panel.panelId;
|
|
145
147
|
const contextLines = [
|
|
@@ -211,13 +213,38 @@ export function useDashboardChatContext(
|
|
|
211
213
|
);
|
|
212
214
|
}, []);
|
|
213
215
|
|
|
216
|
+
useEffect(() => {
|
|
217
|
+
const handleNewChat = () => {
|
|
218
|
+
removeAgentChatContextItem({
|
|
219
|
+
key: DASHBOARD_CONTEXT_KEY,
|
|
220
|
+
openSidebar: false,
|
|
221
|
+
});
|
|
222
|
+
removeAgentChatContextItem({
|
|
223
|
+
key: DASHBOARD_PANEL_CONTEXT_KEY,
|
|
224
|
+
openSidebar: false,
|
|
225
|
+
});
|
|
226
|
+
const publishedSelection = publishedSelectionRef.current;
|
|
227
|
+
publishedSelectionRef.current = null;
|
|
228
|
+
suppressDashboardContextRef.current = true;
|
|
229
|
+
if (publishedSelection) {
|
|
230
|
+
void clearSelectedDashboardObjectIfOwned(publishedSelection);
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
window.addEventListener("agent-chat:new-chat", handleNewChat);
|
|
235
|
+
return () =>
|
|
236
|
+
window.removeEventListener("agent-chat:new-chat", handleNewChat);
|
|
237
|
+
}, []);
|
|
238
|
+
|
|
214
239
|
// Dashboard metadata lands in pieces — title first, then panel count, then
|
|
215
240
|
// the access role. Publishing each piece costs a round-trip and a sync event
|
|
216
241
|
// that invalidates every mounted query, so only the settled value is sent.
|
|
217
242
|
useEffect(() => {
|
|
218
243
|
if (!id) return;
|
|
244
|
+
suppressDashboardContextRef.current = false;
|
|
219
245
|
const displayTitle = title?.trim() || id;
|
|
220
246
|
const timer = setTimeout(() => {
|
|
247
|
+
if (suppressDashboardContextRef.current) return;
|
|
221
248
|
setAgentChatContextItem({
|
|
222
249
|
key: DASHBOARD_CONTEXT_KEY,
|
|
223
250
|
title: `Dashboard: ${displayTitle}`,
|
|
@@ -237,6 +264,7 @@ export function useDashboardChatContext(
|
|
|
237
264
|
|
|
238
265
|
useEffect(() => {
|
|
239
266
|
if (!id || hasSelectedPanel) return;
|
|
267
|
+
if (suppressDashboardContextRef.current) return;
|
|
240
268
|
const displayTitle = title?.trim() || id;
|
|
241
269
|
const selection: SelectedDashboardObject = {
|
|
242
270
|
type: "dashboard",
|
|
@@ -248,6 +276,7 @@ export function useDashboardChatContext(
|
|
|
248
276
|
[SELECTED_OBJECT_SOURCE_FIELD]: TAB_ID,
|
|
249
277
|
};
|
|
250
278
|
const timer = setTimeout(() => {
|
|
279
|
+
if (suppressDashboardContextRef.current) return;
|
|
251
280
|
setClientAppState(SELECTED_OBJECT_STATE_KEY, selection, {
|
|
252
281
|
keepalive: true,
|
|
253
282
|
requestSource: TAB_ID,
|
|
@@ -526,8 +526,7 @@ export function ReplayStorageHint({
|
|
|
526
526
|
const storageStatus = useReplayStorageStatus();
|
|
527
527
|
const builderStatus = useBuilderStatus();
|
|
528
528
|
const builderConnect = useBuilderConnectFlow({
|
|
529
|
-
popupUrl:
|
|
530
|
-
builderStatus.status?.cliAuthUrl ?? builderStatus.status?.connectUrl,
|
|
529
|
+
popupUrl: builderStatus.status?.connectUrl,
|
|
531
530
|
trackingSource: "analytics_sessions_storage_hint",
|
|
532
531
|
trackingFlow: "replay_storage",
|
|
533
532
|
onConnected: async () => {
|
|
@@ -20,6 +20,8 @@ AGENT_CHAT_DURABLE_BACKGROUND = "true"
|
|
|
20
20
|
|
|
21
21
|
[context.production.environment]
|
|
22
22
|
AGENT_NATIVE_HOSTED_HARNESS = "true"
|
|
23
|
+
AGENT_NATIVE_ENABLE_KEEP_WARM = "1"
|
|
24
|
+
AGENT_NATIVE_DISABLE_KEEP_WARM_BACKGROUND = "1"
|
|
23
25
|
# Analytics owns its schema at release time (`migrate:production` above), so
|
|
24
26
|
# production request functions skip migrations. Preview functions still run
|
|
25
27
|
# request-path migrations because previews do not run the production release
|
|
@@ -13,6 +13,8 @@ AGENT_CHAT_DURABLE_BACKGROUND = "true"
|
|
|
13
13
|
|
|
14
14
|
[context.production.environment]
|
|
15
15
|
AGENT_NATIVE_HOSTED_HARNESS = "true"
|
|
16
|
+
AGENT_NATIVE_ENABLE_KEEP_WARM = "1"
|
|
17
|
+
AGENT_NATIVE_DISABLE_KEEP_WARM_BACKGROUND = "1"
|
|
16
18
|
|
|
17
19
|
# Emit the Netlify background function used by Assets' durable agent-chat opt-in.
|
|
18
20
|
# The normal server timeout still protects non-agent image/video HTTP handlers.
|
|
@@ -12,6 +12,8 @@ NPM_CONFIG_PRODUCTION = "false"
|
|
|
12
12
|
|
|
13
13
|
[context.production.environment]
|
|
14
14
|
AGENT_NATIVE_HOSTED_HARNESS = "true"
|
|
15
|
+
AGENT_NATIVE_ENABLE_KEEP_WARM = "1"
|
|
16
|
+
AGENT_NATIVE_DISABLE_KEEP_WARM_BACKGROUND = "1"
|
|
15
17
|
|
|
16
18
|
# A2A delegation calls run a full agent loop (LLM + tool calls + DB queries).
|
|
17
19
|
# The framework does not impose a default agent-run timeout; if a template sets
|
|
@@ -981,7 +981,8 @@ export const DayView = memo(function DayView({
|
|
|
981
981
|
{/* Positioned events overlay */}
|
|
982
982
|
<div
|
|
983
983
|
data-calendar-create-surface="true"
|
|
984
|
-
className="absolute
|
|
984
|
+
className="absolute left-0 right-0 top-0 ml-[40px] mr-2 sm:ml-[56px] sm:mr-4"
|
|
985
|
+
style={{ height: `${hours.length * HOUR_HEIGHT}px` }}
|
|
985
986
|
onPointerDown={(e) => {
|
|
986
987
|
// Only start a create-drag from empty space, not on an event or its resize handles
|
|
987
988
|
if ((e.target as HTMLElement).closest("button")) return;
|
|
@@ -12,6 +12,8 @@ NPM_CONFIG_PRODUCTION = "false"
|
|
|
12
12
|
|
|
13
13
|
[context.production.environment]
|
|
14
14
|
AGENT_NATIVE_HOSTED_HARNESS = "true"
|
|
15
|
+
AGENT_NATIVE_ENABLE_KEEP_WARM = "1"
|
|
16
|
+
AGENT_NATIVE_DISABLE_KEEP_WARM_BACKGROUND = "1"
|
|
15
17
|
|
|
16
18
|
# A2A delegation calls run a full agent loop (LLM + tool calls + DB queries).
|
|
17
19
|
# The framework does not impose a default agent-run timeout; if a template sets
|
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
import { PLAYBACK_SPEED_OPTIONS } from "@/lib/playback-speed";
|
|
35
35
|
import { cn } from "@/lib/utils";
|
|
36
36
|
|
|
37
|
-
import { ReactionsTray } from "./reactions-tray";
|
|
37
|
+
import { ReactionsTray, type ReactionHandler } from "./reactions-tray";
|
|
38
38
|
import { Scrubber, msToClock } from "./scrubber";
|
|
39
39
|
|
|
40
40
|
export const SPEED_OPTIONS = PLAYBACK_SPEED_OPTIONS;
|
|
@@ -73,7 +73,7 @@ export interface PlayerControlsProps {
|
|
|
73
73
|
*/
|
|
74
74
|
showReactionsAndComment?: boolean;
|
|
75
75
|
enableReactions?: boolean;
|
|
76
|
-
onReact?:
|
|
76
|
+
onReact?: ReactionHandler;
|
|
77
77
|
enableComments?: boolean;
|
|
78
78
|
onAddComment?: () => void;
|
|
79
79
|
}
|
|
@@ -306,7 +306,7 @@ export function PlayerControls(props: PlayerControlsProps) {
|
|
|
306
306
|
>
|
|
307
307
|
{enableReactions && onReact ? (
|
|
308
308
|
<div className="pointer-events-auto">
|
|
309
|
-
<ReactionsTray onReact={onReact} />
|
|
309
|
+
<ReactionsTray reactions={reactions} onReact={onReact} />
|
|
310
310
|
</div>
|
|
311
311
|
) : null}
|
|
312
312
|
|
|
@@ -8,3 +8,15 @@ export const REACTION_EMOJIS = [
|
|
|
8
8
|
"🤯",
|
|
9
9
|
"👀",
|
|
10
10
|
] as const;
|
|
11
|
+
|
|
12
|
+
export const REACTION_NAMES: Record<(typeof REACTION_EMOJIS)[number], string> =
|
|
13
|
+
{
|
|
14
|
+
"👍": "thumbs up",
|
|
15
|
+
"❤️": "heart",
|
|
16
|
+
"🔥": "fire",
|
|
17
|
+
"👏": "clap",
|
|
18
|
+
"🎉": "celebrate",
|
|
19
|
+
"😂": "laugh",
|
|
20
|
+
"🤯": "mind blown",
|
|
21
|
+
"👀": "eyes",
|
|
22
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IconCheck } from "@tabler/icons-react";
|
|
2
|
+
import { useMemo, useState } from "react";
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
Tooltip,
|
|
@@ -7,10 +8,20 @@ import {
|
|
|
7
8
|
} from "@/components/ui/tooltip";
|
|
8
9
|
import { cn } from "@/lib/utils";
|
|
9
10
|
|
|
10
|
-
import { REACTION_EMOJIS } from "./reaction-emojis";
|
|
11
|
+
import { REACTION_EMOJIS, REACTION_NAMES } from "./reaction-emojis";
|
|
12
|
+
|
|
13
|
+
export type ReactionHandlerResult = boolean | void | Promise<boolean | void>;
|
|
14
|
+
export type ReactionHandler = (emoji: string) => ReactionHandlerResult;
|
|
15
|
+
|
|
16
|
+
export interface ReactionSummary {
|
|
17
|
+
id: string;
|
|
18
|
+
emoji: string;
|
|
19
|
+
videoTimestampMs: number;
|
|
20
|
+
}
|
|
11
21
|
|
|
12
22
|
export interface ReactionsTrayProps {
|
|
13
|
-
onReact:
|
|
23
|
+
onReact: ReactionHandler;
|
|
24
|
+
reactions?: Pick<ReactionSummary, "emoji">[];
|
|
14
25
|
disabled?: boolean;
|
|
15
26
|
}
|
|
16
27
|
|
|
@@ -22,18 +33,62 @@ interface Float {
|
|
|
22
33
|
|
|
23
34
|
let idc = 0;
|
|
24
35
|
|
|
25
|
-
export function ReactionsTray({
|
|
36
|
+
export function ReactionsTray({
|
|
37
|
+
onReact,
|
|
38
|
+
reactions,
|
|
39
|
+
disabled,
|
|
40
|
+
}: ReactionsTrayProps) {
|
|
26
41
|
const [floats, setFloats] = useState<Float[]>([]);
|
|
42
|
+
const [savingEmoji, setSavingEmoji] = useState<string | null>(null);
|
|
43
|
+
const [savedEmoji, setSavedEmoji] = useState<string | null>(null);
|
|
44
|
+
|
|
45
|
+
const reactionCounts = useMemo(() => {
|
|
46
|
+
const counts: Partial<Record<(typeof REACTION_EMOJIS)[number], number>> =
|
|
47
|
+
{};
|
|
48
|
+
for (const reaction of reactions ?? []) {
|
|
49
|
+
if (REACTION_EMOJIS.includes(reaction.emoji as never)) {
|
|
50
|
+
const emoji = reaction.emoji as (typeof REACTION_EMOJIS)[number];
|
|
51
|
+
counts[emoji] = (counts[emoji] ?? 0) + 1;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return counts;
|
|
55
|
+
}, [reactions]);
|
|
27
56
|
|
|
28
57
|
function fire(emoji: string) {
|
|
29
|
-
if (disabled) return;
|
|
30
|
-
onReact(emoji);
|
|
58
|
+
if (disabled || savingEmoji === emoji) return;
|
|
31
59
|
const id = ++idc;
|
|
32
60
|
const left = 10 + Math.random() * 80; // random horizontal variance within tray
|
|
33
61
|
setFloats((f) => [...f, { id, emoji, left }]);
|
|
34
62
|
setTimeout(() => {
|
|
35
63
|
setFloats((f) => f.filter((x) => x.id !== id));
|
|
36
64
|
}, 2500);
|
|
65
|
+
|
|
66
|
+
let result: ReactionHandlerResult;
|
|
67
|
+
try {
|
|
68
|
+
result = onReact(emoji);
|
|
69
|
+
} catch {
|
|
70
|
+
setSavedEmoji((current) => (current === emoji ? null : current));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (result && typeof result === "object" && "then" in result) {
|
|
75
|
+
setSavingEmoji(emoji);
|
|
76
|
+
void Promise.resolve(result)
|
|
77
|
+
.then((saved) => {
|
|
78
|
+
setSavedEmoji((current) =>
|
|
79
|
+
saved === false ? (current === emoji ? null : current) : emoji,
|
|
80
|
+
);
|
|
81
|
+
})
|
|
82
|
+
.catch(() => {
|
|
83
|
+
setSavedEmoji((current) => (current === emoji ? null : current));
|
|
84
|
+
})
|
|
85
|
+
.finally(() => {
|
|
86
|
+
setSavingEmoji((current) => (current === emoji ? null : current));
|
|
87
|
+
});
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (result !== false) setSavedEmoji(emoji);
|
|
37
92
|
}
|
|
38
93
|
|
|
39
94
|
return (
|
|
@@ -42,17 +97,43 @@ export function ReactionsTray({ onReact, disabled }: ReactionsTrayProps) {
|
|
|
42
97
|
<Tooltip key={emoji}>
|
|
43
98
|
<TooltipTrigger asChild>
|
|
44
99
|
<button
|
|
100
|
+
type="button"
|
|
45
101
|
onClick={() => fire(emoji)}
|
|
46
|
-
disabled={disabled}
|
|
102
|
+
disabled={disabled || savingEmoji === emoji}
|
|
103
|
+
aria-label={
|
|
104
|
+
reactionCounts[emoji]
|
|
105
|
+
? `${REACTION_NAMES[emoji]} ${reactionCounts[emoji]}`
|
|
106
|
+
: `React with ${emoji}`
|
|
107
|
+
}
|
|
108
|
+
aria-pressed={savedEmoji === emoji}
|
|
109
|
+
data-reaction-emoji={emoji}
|
|
110
|
+
data-reaction-count={reactionCounts[emoji] ?? 0}
|
|
111
|
+
data-reaction-saved={savedEmoji === emoji}
|
|
47
112
|
className={cn(
|
|
48
|
-
"flex h-
|
|
113
|
+
"relative flex h-8 w-8 items-center justify-center rounded-full text-base transition-[background-color,transform,box-shadow] hover:scale-110 hover:bg-accent sm:h-9 sm:w-9 sm:text-xl",
|
|
114
|
+
savedEmoji === emoji &&
|
|
115
|
+
"bg-accent shadow-[0_0_0_2px_hsl(var(--primary)/0.35)]",
|
|
116
|
+
savingEmoji === emoji && "animate-pulse",
|
|
49
117
|
disabled && "opacity-50 cursor-not-allowed",
|
|
50
118
|
)}
|
|
51
119
|
>
|
|
52
120
|
{emoji}
|
|
121
|
+
{savedEmoji === emoji ? (
|
|
122
|
+
<span className="absolute -right-0.5 -top-0.5 flex h-3.5 w-3.5 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-sm">
|
|
123
|
+
<IconCheck className="h-2.5 w-2.5" stroke={3} />
|
|
124
|
+
</span>
|
|
125
|
+
) : reactionCounts[emoji] ? (
|
|
126
|
+
<span className="absolute -right-0.5 -top-0.5 min-w-3.5 rounded-full bg-muted px-0.5 text-[9px] font-semibold leading-3 text-muted-foreground shadow-sm">
|
|
127
|
+
{reactionCounts[emoji]}
|
|
128
|
+
</span>
|
|
129
|
+
) : null}
|
|
53
130
|
</button>
|
|
54
131
|
</TooltipTrigger>
|
|
55
|
-
<TooltipContent>
|
|
132
|
+
<TooltipContent>
|
|
133
|
+
{reactionCounts[emoji]
|
|
134
|
+
? `${REACTION_NAMES[emoji]} ${reactionCounts[emoji]}`
|
|
135
|
+
: `React with ${emoji}`}
|
|
136
|
+
</TooltipContent>
|
|
56
137
|
</Tooltip>
|
|
57
138
|
))}
|
|
58
139
|
|
|
@@ -28,6 +28,7 @@ export function Scrubber(props: ScrubberProps) {
|
|
|
28
28
|
const [tooltip, setTooltip] = useState<
|
|
29
29
|
| { kind: "comment"; content: string; ms: number }
|
|
30
30
|
| { kind: "chapter"; title: string; ms: number }
|
|
31
|
+
| { kind: "reaction"; content: string; ms: number }
|
|
31
32
|
| null
|
|
32
33
|
>(null);
|
|
33
34
|
|
|
@@ -109,6 +110,17 @@ export function Scrubber(props: ScrubberProps) {
|
|
|
109
110
|
return map;
|
|
110
111
|
}, [comments]);
|
|
111
112
|
|
|
113
|
+
const reactionsByMs = useMemo(() => {
|
|
114
|
+
const map = new Map<number, { id: string; emoji: string }[]>();
|
|
115
|
+
recentReactions.forEach((reaction) => {
|
|
116
|
+
const key = Math.round(reaction.videoTimestampMs / 500) * 500;
|
|
117
|
+
const list = map.get(key) ?? [];
|
|
118
|
+
list.push({ id: reaction.id, emoji: reaction.emoji });
|
|
119
|
+
map.set(key, list);
|
|
120
|
+
});
|
|
121
|
+
return map;
|
|
122
|
+
}, [recentReactions]);
|
|
123
|
+
|
|
112
124
|
return (
|
|
113
125
|
<div
|
|
114
126
|
className="relative h-10 flex items-center touch-none cursor-pointer"
|
|
@@ -142,7 +154,9 @@ export function Scrubber(props: ScrubberProps) {
|
|
|
142
154
|
className="absolute -top-10 -translate-x-1/2 max-w-[240px] rounded bg-primary px-2 py-1 text-[11px] text-primary-foreground"
|
|
143
155
|
style={{ left: (tooltip.ms / Math.max(1, durationMs)) * 100 + "%" }}
|
|
144
156
|
>
|
|
145
|
-
{tooltip.kind === "comment"
|
|
157
|
+
{tooltip.kind === "comment" || tooltip.kind === "reaction"
|
|
158
|
+
? tooltip.content
|
|
159
|
+
: tooltip.title}
|
|
146
160
|
</div>
|
|
147
161
|
) : null}
|
|
148
162
|
|
|
@@ -208,17 +222,36 @@ export function Scrubber(props: ScrubberProps) {
|
|
|
208
222
|
</button>
|
|
209
223
|
))}
|
|
210
224
|
|
|
211
|
-
{/* Reaction
|
|
212
|
-
{
|
|
213
|
-
<
|
|
214
|
-
key={
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
225
|
+
{/* Reaction markers */}
|
|
226
|
+
{Array.from(reactionsByMs.entries()).map(([ms, list]) => (
|
|
227
|
+
<button
|
|
228
|
+
key={`reaction-${ms}`}
|
|
229
|
+
type="button"
|
|
230
|
+
data-player-reaction-marker
|
|
231
|
+
onPointerDown={(event) => event.stopPropagation()}
|
|
232
|
+
onMouseEnter={() =>
|
|
233
|
+
setTooltip({
|
|
234
|
+
kind: "reaction",
|
|
235
|
+
content: `${list.map((reaction) => reaction.emoji).join(" ")} · ${list.length} reaction${list.length === 1 ? "" : "s"}`,
|
|
236
|
+
ms,
|
|
237
|
+
})
|
|
238
|
+
}
|
|
239
|
+
onMouseLeave={() => setTooltip(null)}
|
|
240
|
+
onClick={(event) => {
|
|
241
|
+
event.stopPropagation();
|
|
242
|
+
onSeek(ms);
|
|
218
243
|
}}
|
|
244
|
+
className="absolute -bottom-5 flex h-5 min-w-5 -translate-x-1/2 items-center justify-center rounded-full bg-background/95 px-1 text-sm shadow-md ring-1 ring-primary/40 transition-transform hover:scale-125"
|
|
245
|
+
style={{ left: (ms / Math.max(1, durationMs)) * 100 + "%" }}
|
|
246
|
+
aria-label={`${list.length} reaction${list.length === 1 ? "" : "s"} at ${msToClock(ms)}`}
|
|
219
247
|
>
|
|
220
|
-
{
|
|
221
|
-
|
|
248
|
+
{list[0].emoji}
|
|
249
|
+
{list.length > 1 ? (
|
|
250
|
+
<span className="absolute -right-1.5 -top-1.5 flex h-3.5 min-w-3.5 items-center justify-center rounded-full bg-primary px-0.5 text-[8px] font-bold leading-none text-primary-foreground ring-1 ring-background">
|
|
251
|
+
{list.length}
|
|
252
|
+
</span>
|
|
253
|
+
) : null}
|
|
254
|
+
</button>
|
|
222
255
|
))}
|
|
223
256
|
|
|
224
257
|
{/* Thumb */}
|
|
@@ -57,6 +57,11 @@ import {
|
|
|
57
57
|
type PlaybackComment,
|
|
58
58
|
} from "./playback-comment-overlay";
|
|
59
59
|
import { PlayerControls, SPEED_OPTIONS } from "./player-controls";
|
|
60
|
+
import type {
|
|
61
|
+
ReactionHandler,
|
|
62
|
+
ReactionHandlerResult,
|
|
63
|
+
ReactionSummary,
|
|
64
|
+
} from "./reactions-tray";
|
|
60
65
|
|
|
61
66
|
function resolveLocalUrl(url: string | null | undefined): string | undefined {
|
|
62
67
|
if (!url) return undefined;
|
|
@@ -251,7 +256,7 @@ export interface VideoPlayerProps {
|
|
|
251
256
|
* visible only while `isFullscreen` is true.
|
|
252
257
|
*/
|
|
253
258
|
enableReactions?: boolean;
|
|
254
|
-
onReact?:
|
|
259
|
+
onReact?: ReactionHandler;
|
|
255
260
|
enableComments?: boolean;
|
|
256
261
|
/**
|
|
257
262
|
* Opens the existing comment composer/panel. While fullscreen, the caller
|
|
@@ -374,6 +379,10 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
|
|
|
374
379
|
const [currentMs, setCurrentMs] = useState(startMs ?? 0);
|
|
375
380
|
currentMsRef.current = currentMs;
|
|
376
381
|
isPlayingRef.current = isPlaying;
|
|
382
|
+
const [optimisticReactions, setOptimisticReactions] = useState<
|
|
383
|
+
ReactionSummary[]
|
|
384
|
+
>([]);
|
|
385
|
+
const optimisticReactionIdRef = useRef(0);
|
|
377
386
|
const [loomStartMs, setLoomStartMs] = useState<number | null>(null);
|
|
378
387
|
const [volume, setVolume] = useState(1);
|
|
379
388
|
// Autoplaying players (e.g. the Slack unfurl embed, `?autoplay=1`) must
|
|
@@ -492,20 +501,101 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
|
|
|
492
501
|
? []
|
|
493
502
|
: [{ startMs: editedMs, title: chapter.title }];
|
|
494
503
|
}),
|
|
495
|
-
reactions:
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
504
|
+
reactions: [
|
|
505
|
+
...(reactions ?? []).flatMap((reaction) => {
|
|
506
|
+
const editedMs = mapMarker(reaction.videoTimestampMs);
|
|
507
|
+
return editedMs === null
|
|
508
|
+
? []
|
|
509
|
+
: [
|
|
510
|
+
{
|
|
511
|
+
id: reaction.id,
|
|
512
|
+
emoji: reaction.emoji,
|
|
513
|
+
videoTimestampMs: editedMs,
|
|
514
|
+
},
|
|
515
|
+
];
|
|
516
|
+
}),
|
|
517
|
+
...optimisticReactions.flatMap((reaction) => {
|
|
518
|
+
const editedMs = mapMarker(reaction.videoTimestampMs);
|
|
519
|
+
return editedMs === null
|
|
520
|
+
? []
|
|
521
|
+
: [
|
|
522
|
+
{
|
|
523
|
+
id: reaction.id,
|
|
524
|
+
emoji: reaction.emoji,
|
|
525
|
+
videoTimestampMs: editedMs,
|
|
526
|
+
},
|
|
527
|
+
];
|
|
528
|
+
}),
|
|
529
|
+
],
|
|
507
530
|
};
|
|
508
|
-
}, [
|
|
531
|
+
}, [
|
|
532
|
+
chapters,
|
|
533
|
+
comments,
|
|
534
|
+
currentMs,
|
|
535
|
+
edits,
|
|
536
|
+
optimisticReactions,
|
|
537
|
+
reactions,
|
|
538
|
+
resolvedDurationMs,
|
|
539
|
+
]);
|
|
540
|
+
|
|
541
|
+
useEffect(() => {
|
|
542
|
+
if (!optimisticReactions.length || !reactions?.length) return;
|
|
543
|
+
setOptimisticReactions((current) => {
|
|
544
|
+
const next = current.filter(
|
|
545
|
+
(optimistic) =>
|
|
546
|
+
!reactions.some(
|
|
547
|
+
(persisted) =>
|
|
548
|
+
persisted.emoji === optimistic.emoji &&
|
|
549
|
+
Math.abs(
|
|
550
|
+
persisted.videoTimestampMs - optimistic.videoTimestampMs,
|
|
551
|
+
) < 1000,
|
|
552
|
+
),
|
|
553
|
+
);
|
|
554
|
+
return next.length === current.length ? current : next;
|
|
555
|
+
});
|
|
556
|
+
}, [optimisticReactions.length, reactions]);
|
|
557
|
+
|
|
558
|
+
const handleReact = useCallback<ReactionHandler>(
|
|
559
|
+
(emoji) => {
|
|
560
|
+
const optimistic = {
|
|
561
|
+
id: `optimistic-reaction-${++optimisticReactionIdRef.current}`,
|
|
562
|
+
emoji,
|
|
563
|
+
videoTimestampMs: currentMsRef.current,
|
|
564
|
+
};
|
|
565
|
+
setOptimisticReactions((current) => [...current, optimistic]);
|
|
566
|
+
|
|
567
|
+
const removeOptimistic = () => {
|
|
568
|
+
setOptimisticReactions((current) =>
|
|
569
|
+
current.filter((reaction) => reaction.id !== optimistic.id),
|
|
570
|
+
);
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
let result: ReactionHandlerResult | undefined;
|
|
574
|
+
try {
|
|
575
|
+
result = onReact?.(emoji);
|
|
576
|
+
} catch {
|
|
577
|
+
removeOptimistic();
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
if (result && typeof result === "object" && "then" in result) {
|
|
582
|
+
return Promise.resolve(result).then(
|
|
583
|
+
(saved) => {
|
|
584
|
+
if (saved === false) removeOptimistic();
|
|
585
|
+
return saved !== false;
|
|
586
|
+
},
|
|
587
|
+
() => {
|
|
588
|
+
removeOptimistic();
|
|
589
|
+
return false;
|
|
590
|
+
},
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
if (result === false) removeOptimistic();
|
|
595
|
+
return result !== false;
|
|
596
|
+
},
|
|
597
|
+
[onReact],
|
|
598
|
+
);
|
|
509
599
|
const activeVideoSourceIdentity = useMemo(
|
|
510
600
|
() => videoSourceIdentity(activeVideoSrc),
|
|
511
601
|
[activeVideoSrc],
|
|
@@ -2016,7 +2106,7 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
|
|
|
2016
2106
|
menuPortalContainer={fullscreenMenuContainer}
|
|
2017
2107
|
showReactionsAndComment={isFullscreen}
|
|
2018
2108
|
enableReactions={enableReactions}
|
|
2019
|
-
onReact={
|
|
2109
|
+
onReact={handleReact}
|
|
2020
2110
|
enableComments={enableComments}
|
|
2021
2111
|
onAddComment={onAddComment}
|
|
2022
2112
|
/>
|
|
@@ -106,8 +106,7 @@ export default function SettingsIndexRoute() {
|
|
|
106
106
|
const builderStatus = useBuilderStatus();
|
|
107
107
|
const connectRequestedRef = useRef(false);
|
|
108
108
|
const builderConnect = useBuilderConnectFlow({
|
|
109
|
-
popupUrl:
|
|
110
|
-
builderStatus.status?.cliAuthUrl ?? builderStatus.status?.connectUrl,
|
|
109
|
+
popupUrl: builderStatus.status?.connectUrl,
|
|
111
110
|
trackingSource: "clips_settings",
|
|
112
111
|
trackingFlow: "clips_setup",
|
|
113
112
|
onConnected: async () => {
|
|
@@ -4,6 +4,7 @@ import { redirect, type LoaderFunctionArgs } from "react-router";
|
|
|
4
4
|
const SEO_TITLE = "Clips - Open Source screen recorder";
|
|
5
5
|
const SEO_DESCRIPTION =
|
|
6
6
|
"Open Source screen recorder and meeting-notes app with AI transcripts, summaries, search, dictation, and agent-readable share links.";
|
|
7
|
+
const SSR_REDIRECT_CONTENT_TYPE = "text/html; charset=utf-8";
|
|
7
8
|
|
|
8
9
|
export function meta() {
|
|
9
10
|
return [
|
|
@@ -38,11 +39,18 @@ function buildTarget(url: URL): string {
|
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
export function loader({ url }: LoaderFunctionArgs) {
|
|
41
|
-
|
|
42
|
+
const response = redirect(buildTarget(url));
|
|
43
|
+
// The deploy adapter uses the HTML content type to apply Clips' public SSR
|
|
44
|
+
// cache policy. Without it, the root redirect reaches origin on every visit
|
|
45
|
+
// even though the /library shell is already cached at the edge.
|
|
46
|
+
response.headers.set("content-type", SSR_REDIRECT_CONTENT_TYPE);
|
|
47
|
+
throw response;
|
|
42
48
|
}
|
|
43
49
|
|
|
44
50
|
export function clientLoader({ url }: LoaderFunctionArgs) {
|
|
45
|
-
|
|
51
|
+
const response = redirect(buildTarget(url));
|
|
52
|
+
response.headers.set("content-type", SSR_REDIRECT_CONTENT_TYPE);
|
|
53
|
+
throw response;
|
|
46
54
|
}
|
|
47
55
|
|
|
48
56
|
export function HydrateFallback() {
|
|
@@ -1628,7 +1628,7 @@ export default function RecordingPage() {
|
|
|
1628
1628
|
onReact={(emoji) => {
|
|
1629
1629
|
tracking.reportReaction(emoji);
|
|
1630
1630
|
const liveMs = resolvePlaybackMs();
|
|
1631
|
-
fetch(
|
|
1631
|
+
return fetch(
|
|
1632
1632
|
agentNativePath(
|
|
1633
1633
|
"/_agent-native/actions/react-to-recording",
|
|
1634
1634
|
),
|
|
@@ -1647,9 +1647,11 @@ export default function RecordingPage() {
|
|
|
1647
1647
|
throw new Error(`react failed: ${res.status}`);
|
|
1648
1648
|
return playerDataQ.refetch();
|
|
1649
1649
|
})
|
|
1650
|
-
.
|
|
1651
|
-
|
|
1652
|
-
|
|
1650
|
+
.then(() => true)
|
|
1651
|
+
.catch((err) => {
|
|
1652
|
+
console.warn("[clips] react failed", err);
|
|
1653
|
+
return false;
|
|
1654
|
+
});
|
|
1653
1655
|
}}
|
|
1654
1656
|
className="h-full w-full rounded-none sm:rounded-xl"
|
|
1655
1657
|
/>
|
|
@@ -1736,11 +1738,12 @@ export default function RecordingPage() {
|
|
|
1736
1738
|
) : null}
|
|
1737
1739
|
{recording.enableReactions ? (
|
|
1738
1740
|
<ReactionsTray
|
|
1741
|
+
reactions={reactions}
|
|
1739
1742
|
disabled={!recording.enableReactions || !canComment}
|
|
1740
1743
|
onReact={(emoji) => {
|
|
1741
1744
|
tracking.reportReaction(emoji);
|
|
1742
1745
|
const liveMs = resolvePlaybackMs();
|
|
1743
|
-
fetch(
|
|
1746
|
+
return fetch(
|
|
1744
1747
|
agentNativePath(
|
|
1745
1748
|
"/_agent-native/actions/react-to-recording",
|
|
1746
1749
|
),
|
|
@@ -1759,9 +1762,11 @@ export default function RecordingPage() {
|
|
|
1759
1762
|
throw new Error(`react failed: ${res.status}`);
|
|
1760
1763
|
return playerDataQ.refetch();
|
|
1761
1764
|
})
|
|
1762
|
-
.
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
+
.then(() => true)
|
|
1766
|
+
.catch((err) => {
|
|
1767
|
+
console.warn("[clips] react failed", err);
|
|
1768
|
+
return false;
|
|
1769
|
+
});
|
|
1765
1770
|
}}
|
|
1766
1771
|
/>
|
|
1767
1772
|
) : null}
|