@agent-native/core 0.98.0 → 0.98.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/core/CHANGELOG.md +24 -0
- package/corpus/core/package.json +2 -2
- package/corpus/core/src/action-change-marker.ts +8 -1
- package/corpus/core/src/changelog/parse.ts +8 -3
- package/corpus/core/src/client/AgentPanel.tsx +2 -3
- package/corpus/core/src/client/AssistantChat.tsx +83 -39
- package/corpus/core/src/client/analytics.ts +29 -0
- package/corpus/core/src/client/blocks/library/wireframe-kit.tsx +16 -2
- package/corpus/core/src/client/chat-model-groups.ts +28 -0
- package/corpus/core/src/client/composer/RealtimeVoiceMode.tsx +442 -85
- package/corpus/core/src/client/composer/TiptapComposer.tsx +46 -29
- package/corpus/core/src/client/composer/VoiceButton.tsx +2 -1
- package/corpus/core/src/client/composer/index.ts +5 -0
- package/corpus/core/src/client/composer/useRealtimeVoiceMode.tsx +1206 -67
- package/corpus/core/src/client/index.ts +5 -0
- package/corpus/core/src/client/progress/RunsTray.tsx +13 -1
- package/corpus/core/src/client/settings/SecretsSection.tsx +387 -266
- package/corpus/core/src/client/settings/SettingsPanel.tsx +117 -38
- package/corpus/core/src/client/use-action.ts +12 -0
- package/corpus/core/src/client/use-agent-chat-home-handoff.ts +8 -1
- package/corpus/core/src/client/use-agent-engine-configured.ts +17 -3
- package/corpus/core/src/client/use-db-sync.ts +84 -34
- package/corpus/core/src/client/use-pausing-interval.ts +14 -4
- package/corpus/core/src/integrations/scope-store.ts +1 -1
- package/corpus/core/src/integrations/usage-budget-store.ts +1 -1
- package/corpus/core/src/localization/default-messages.ts +44 -1
- package/corpus/core/src/mcp-client/workspace-servers.ts +4 -4
- package/corpus/core/src/secrets/crypto.ts +28 -8
- package/corpus/core/src/server/action-change.ts +5 -1
- package/corpus/core/src/server/action-routes.ts +8 -0
- package/corpus/core/src/server/poll.ts +1 -0
- package/corpus/core/src/server/realtime-voice.ts +299 -13
- package/corpus/core/src/styles/agent-native.css +2 -2
- package/corpus/core/src/templates/default/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/core/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/core/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +7 -3
- package/corpus/core/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +39 -9
- package/corpus/core/src/vite/client.ts +9 -1
- package/corpus/templates/analytics/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +10 -5
- package/corpus/templates/analytics/AGENTS.md +9 -13
- package/corpus/templates/analytics/actions/navigate.ts +59 -5
- package/corpus/templates/analytics/actions/view-screen.ts +3 -6
- package/corpus/templates/analytics/app/components/ProviderCorpusJobNotifier.tsx +20 -3
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +2 -1
- package/corpus/templates/analytics/app/components/layout/Header.tsx +1 -1
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +21 -6
- package/corpus/templates/analytics/app/global.css +19 -20
- package/corpus/templates/analytics/app/lib/provider-corpus-job-sync.ts +26 -0
- package/corpus/templates/analytics/app/pages/Settings.tsx +35 -341
- package/corpus/templates/analytics/app/pages/monitoring/ErrorsPanel.tsx +38 -7
- package/corpus/templates/analytics/app/pages/monitoring/MonitoringPage.tsx +1 -1
- package/corpus/templates/analytics/app/pages/monitoring/errors/i18n.ts +3 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +333 -272
- package/corpus/templates/analytics/app/pages/sessions/SessionDevToolsPanel.tsx +72 -17
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +297 -108
- package/corpus/templates/analytics/app/root.tsx +28 -8
- package/corpus/templates/analytics/changelog/2026-06-24-first-party-signup-and-pageview-panels-now-honor-dashboard-filters.md +2 -1
- package/corpus/templates/analytics/changelog/2026-06-25-referrer-dashboard-panels-show-external-domains.md +2 -1
- package/corpus/templates/analytics/changelog/2026-07-06-mobile-chat-header-is-more-compact-and-the-menu-button-opens-navigation.md +1 -0
- package/corpus/templates/analytics/changelog/2026-07-08-dashboard-history-and-chat-context.md +1 -0
- package/corpus/templates/analytics/changelog/2026-07-08-settings-are-cleaner-searchable-and-alerts-have-their-own-tab.md +1 -0
- package/corpus/templates/analytics/changelog/2026-07-08-sidebar-sections-stay-collapsed-until-opened.md +1 -0
- package/corpus/templates/analytics/changelog/2026-07-11-analytics-no-longer-repeatedly-restarts-background-requests-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-analytics-only-asks-for-replay-storage-when-you-enable-sessi.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-charts-now-show-their-loading-skeleton-while-refreshing.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-realtime-voice-now-shows-a-clear-connection-indicator-comple.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-replay-event-counts-stay-hidden-until-recordings-finish-load.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-replay-storage-key-mismatches-show-actionable-setup-guidance.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-replay-timelines-group-continuous-scrolling-and-dev-tools-co.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-session-filters-now-stay-compact-in-a-settings-menu-at-mediu.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-session-replay-console-errors-now-link-to-matching-issues-or.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-session-replays-load-large-recordings-faster-and-surface-fai.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-session-replays-preserve-recorded-styles-and-viewport-layout.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-session-replays-stay-centered-instead-of-clipping-wide-recor.md +6 -0
- package/corpus/templates/analytics/package.json +1 -1
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +1 -303
- package/corpus/templates/analytics/server/handlers/session-replay.ts +57 -0
- package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +1 -1
- package/corpus/templates/analytics/server/lib/demo-dashboards.ts +16 -3
- package/corpus/templates/analytics/server/lib/session-replay-agent-context.ts +12 -0
- package/corpus/templates/analytics/server/lib/session-replay.ts +208 -2
- package/corpus/templates/analytics/server/plugins/onboarding.ts +7 -79
- package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId]/chunks.get.ts +1 -0
- package/corpus/templates/assets/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/templates/assets/app/global.css +4 -4
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +0 -1
- package/corpus/templates/assets/app/routes/library.tsx +0 -1
- package/corpus/templates/brain/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/templates/brain/app/global.css +5 -5
- package/corpus/templates/brain/app/root.tsx +0 -3
- package/corpus/templates/calendar/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/templates/calendar/app/root.tsx +1 -0
- package/corpus/templates/chat/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/templates/clips/app/hooks/use-auto-title.ts +74 -8
- package/corpus/templates/clips/app/hooks/use-library.ts +12 -25
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +20 -22
- package/corpus/templates/clips/changelog/2026-07-11-generated-titles-no-longer-cause-continuous-background-refreshes.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-11-s3-storage-setup-stays-collapsed-until-you-choose-it-as-the-.md +6 -0
- package/corpus/templates/content/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/templates/content/app/hooks/use-comments.ts +0 -1
- package/corpus/templates/content/changelog/2026-07-11-comments-now-update-from-live-collaboration-events-without-c.md +6 -0
- package/corpus/templates/design/app/components/layout/Header.tsx +1 -1
- package/corpus/templates/forms/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/templates/macros/app/components/layout/AppLayout.tsx +1 -2
- package/corpus/templates/macros/app/root.tsx +0 -3
- package/corpus/templates/mail/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/templates/mail/app/hooks/use-compose-state.ts +1 -0
- package/corpus/templates/mail/app/root.tsx +1 -12
- package/corpus/templates/plan/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/templates/plan/app/hooks/use-navigation-state.ts +5 -2
- package/corpus/templates/plan/app/hooks/use-plans.ts +6 -13
- package/corpus/templates/plan/app/pages/PlansPage.tsx +5 -27
- package/corpus/templates/plan/changelog/2026-07-11-plan-refreshes-only-when-content-changes.md +6 -0
- package/corpus/templates/slides/app/components/comments/SlideCommentsPanel.tsx +4 -1
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +1 -1
- package/corpus/templates/slides/app/components/layout/Header.tsx +1 -1
- package/corpus/templates/slides/app/context/DeckContext.tsx +1 -0
- package/corpus/templates/slides/app/hooks/use-slide-comments.ts +7 -31
- package/corpus/templates/slides/changelog/2026-07-11-comments-now-update-from-live-collaboration-events-without-c.md +6 -0
- package/dist/action-change-marker.d.ts +1 -0
- package/dist/action-change-marker.d.ts.map +1 -1
- package/dist/action-change-marker.js +7 -1
- package/dist/action-change-marker.js.map +1 -1
- package/dist/changelog/parse.d.ts +4 -2
- package/dist/changelog/parse.d.ts.map +1 -1
- package/dist/changelog/parse.js +5 -3
- package/dist/changelog/parse.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +2 -3
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +7 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +62 -40
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +26 -0
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/blocks/library/wireframe-kit.d.ts +2 -0
- package/dist/client/blocks/library/wireframe-kit.d.ts.map +1 -1
- package/dist/client/blocks/library/wireframe-kit.js +9 -2
- package/dist/client/blocks/library/wireframe-kit.js.map +1 -1
- package/dist/client/chat-model-groups.d.ts.map +1 -1
- package/dist/client/chat-model-groups.js +24 -0
- package/dist/client/chat-model-groups.js.map +1 -1
- package/dist/client/composer/RealtimeVoiceMode.d.ts +40 -4
- package/dist/client/composer/RealtimeVoiceMode.d.ts.map +1 -1
- package/dist/client/composer/RealtimeVoiceMode.js +172 -42
- package/dist/client/composer/RealtimeVoiceMode.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts +4 -0
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +47 -29
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/composer/VoiceButton.d.ts.map +1 -1
- package/dist/client/composer/VoiceButton.js +2 -2
- package/dist/client/composer/VoiceButton.js.map +1 -1
- package/dist/client/composer/index.d.ts +2 -2
- package/dist/client/composer/index.d.ts.map +1 -1
- package/dist/client/composer/index.js +1 -1
- package/dist/client/composer/index.js.map +1 -1
- package/dist/client/composer/useRealtimeVoiceMode.d.ts +100 -3
- package/dist/client/composer/useRealtimeVoiceMode.d.ts.map +1 -1
- package/dist/client/composer/useRealtimeVoiceMode.js +952 -51
- package/dist/client/composer/useRealtimeVoiceMode.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/progress/RunsTray.d.ts.map +1 -1
- package/dist/client/progress/RunsTray.js +10 -1
- package/dist/client/progress/RunsTray.js.map +1 -1
- package/dist/client/settings/SecretsSection.d.ts +2 -3
- package/dist/client/settings/SecretsSection.d.ts.map +1 -1
- package/dist/client/settings/SecretsSection.js +46 -22
- package/dist/client/settings/SecretsSection.js.map +1 -1
- package/dist/client/settings/SettingsPanel.d.ts +9 -0
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +46 -12
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/use-action.d.ts +2 -0
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +10 -0
- package/dist/client/use-action.js.map +1 -1
- package/dist/client/use-agent-chat-home-handoff.d.ts +2 -0
- package/dist/client/use-agent-chat-home-handoff.d.ts.map +1 -1
- package/dist/client/use-agent-chat-home-handoff.js +7 -1
- package/dist/client/use-agent-chat-home-handoff.js.map +1 -1
- package/dist/client/use-agent-engine-configured.d.ts.map +1 -1
- package/dist/client/use-agent-engine-configured.js +17 -3
- package/dist/client/use-agent-engine-configured.js.map +1 -1
- package/dist/client/use-db-sync.d.ts +8 -2
- package/dist/client/use-db-sync.d.ts.map +1 -1
- package/dist/client/use-db-sync.js +57 -32
- package/dist/client/use-db-sync.js.map +1 -1
- package/dist/client/use-pausing-interval.d.ts.map +1 -1
- package/dist/client/use-pausing-interval.js +16 -4
- package/dist/client/use-pausing-interval.js.map +1 -1
- package/dist/integrations/scope-store.js +1 -1
- package/dist/integrations/scope-store.js.map +1 -1
- package/dist/integrations/usage-budget-store.js +1 -1
- package/dist/integrations/usage-budget-store.js.map +1 -1
- package/dist/localization/default-messages.d.ts +41 -1
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +41 -1
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/mcp-client/workspace-servers.js +4 -4
- package/dist/mcp-client/workspace-servers.js.map +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/secrets/crypto.d.ts +7 -6
- package/dist/secrets/crypto.d.ts.map +1 -1
- package/dist/secrets/crypto.js +30 -11
- package/dist/secrets/crypto.js.map +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/action-change.d.ts +1 -0
- package/dist/server/action-change.d.ts.map +1 -1
- package/dist/server/action-change.js +4 -1
- package/dist/server/action-change.js.map +1 -1
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +5 -0
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/poll.d.ts.map +1 -1
- package/dist/server/poll.js +1 -0
- package/dist/server/poll.js.map +1 -1
- package/dist/server/realtime-voice.d.ts +7 -0
- package/dist/server/realtime-voice.d.ts.map +1 -1
- package/dist/server/realtime-voice.js +222 -12
- package/dist/server/realtime-voice.js.map +1 -1
- package/dist/styles/agent-native.css +2 -2
- package/dist/templates/default/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/dist/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/dist/templates/workspace-core/.agents/skills/secrets/SKILL.md +7 -3
- package/dist/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +39 -9
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +6 -1
- package/dist/vite/client.js.map +1 -1
- package/package.json +2 -2
- package/src/templates/default/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +7 -3
- package/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +39 -9
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
callAction,
|
|
17
17
|
sendToAgentChat,
|
|
18
18
|
type AgentChatMessage,
|
|
19
|
+
useChangeVersions,
|
|
19
20
|
} from "@agent-native/core/client";
|
|
20
21
|
import { fullVideoAiModelSelection } from "@shared/clips-ai-prefs";
|
|
21
22
|
import { useEffect, useRef } from "react";
|
|
@@ -23,7 +24,6 @@ import { useEffect, useRef } from "react";
|
|
|
23
24
|
import { useRecordings, type RecordingSummary } from "./use-library";
|
|
24
25
|
|
|
25
26
|
const DEFAULT_TITLE = "Untitled recording";
|
|
26
|
-
const POLL_INTERVAL_MS = 3000;
|
|
27
27
|
const TWO_MINUTES_MS = 2 * 60 * 1000;
|
|
28
28
|
|
|
29
29
|
/** True when `title` is blank or equal to the server-seeded default. */
|
|
@@ -99,8 +99,9 @@ async function clearRequest(recordingId: string): Promise<void> {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
/**
|
|
102
|
-
* Mount this once in the app shell. It
|
|
103
|
-
*
|
|
102
|
+
* Mount this once in the app shell. It watches the exact application-state
|
|
103
|
+
* keys used for queued Clips AI work and fires `sendToAgentChat` for every
|
|
104
|
+
* pending request queued by a clips action.
|
|
104
105
|
* Idempotent — a given (recordingId, kind, requestedAt) is only dispatched
|
|
105
106
|
* once per tab session.
|
|
106
107
|
*/
|
|
@@ -119,13 +120,26 @@ export function useAutoTitleBridge(): void {
|
|
|
119
120
|
`${r.id}:${r.titleSource ?? ""}:${r.title}:${r.updatedAt}:${r.transcriptStatus ?? ""}:${r.transcriptHasText ? "1" : "0"}`,
|
|
120
121
|
)
|
|
121
122
|
.join("|");
|
|
123
|
+
const aiRequestVersion = useChangeVersions(
|
|
124
|
+
readyRecordings.map(
|
|
125
|
+
(recording) => `app-state:clips-ai-request-${recording.id}`,
|
|
126
|
+
),
|
|
127
|
+
);
|
|
122
128
|
|
|
123
129
|
useEffect(() => {
|
|
124
130
|
if (readyRecordings.length === 0) return;
|
|
125
131
|
let cancelled = false;
|
|
132
|
+
let fallbackTimer: ReturnType<typeof setTimeout> | null = null;
|
|
126
133
|
|
|
127
134
|
async function tick() {
|
|
128
|
-
if (cancelled
|
|
135
|
+
if (cancelled) return;
|
|
136
|
+
if (inflight.current) {
|
|
137
|
+
// A new request-state version can arrive while the previous list read
|
|
138
|
+
// is in flight. Recheck after it settles so that event is not the last
|
|
139
|
+
// chance to dispatch the queued work.
|
|
140
|
+
fallbackTimer = setTimeout(() => void tick(), 50);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
129
143
|
inflight.current = true;
|
|
130
144
|
try {
|
|
131
145
|
const requestsById = await listRequests();
|
|
@@ -192,14 +206,66 @@ export function useAutoTitleBridge(): void {
|
|
|
192
206
|
}
|
|
193
207
|
}
|
|
194
208
|
|
|
195
|
-
|
|
196
|
-
|
|
209
|
+
function scheduleNextFallback() {
|
|
210
|
+
if (cancelled) return;
|
|
211
|
+
const delay = nextAutoTitleFallbackDelay(
|
|
212
|
+
readyRecordings,
|
|
213
|
+
dispatched.current,
|
|
214
|
+
);
|
|
215
|
+
if (delay === null) return;
|
|
216
|
+
// Keep the timeout non-zero so a failed request cannot spin a tight loop.
|
|
217
|
+
fallbackTimer = setTimeout(
|
|
218
|
+
() => {
|
|
219
|
+
fallbackTimer = null;
|
|
220
|
+
void tick().finally(scheduleNextFallback);
|
|
221
|
+
},
|
|
222
|
+
Math.max(delay, 50),
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// One initial read catches requests queued before this component mounted.
|
|
227
|
+
// Later reads are driven by the exact clips-ai-request application-state
|
|
228
|
+
// counters above. The only timer left is a one-shot wake-up when an old
|
|
229
|
+
// transcript-backed recording becomes eligible for the legacy fallback.
|
|
230
|
+
void tick().finally(scheduleNextFallback);
|
|
197
231
|
return () => {
|
|
198
232
|
cancelled = true;
|
|
199
|
-
|
|
233
|
+
if (fallbackTimer) clearTimeout(fallbackTimer);
|
|
200
234
|
};
|
|
235
|
+
// readyRecordingsKey is the stable snapshot consumed by tick; depending on
|
|
236
|
+
// the array itself would restart the effect on every query result object.
|
|
201
237
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
202
|
-
}, [readyRecordingsKey]);
|
|
238
|
+
}, [aiRequestVersion, readyRecordingsKey]);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function nextAutoTitleFallbackDelay(
|
|
242
|
+
recordings: readonly RecordingSummary[],
|
|
243
|
+
dispatched: ReadonlySet<string>,
|
|
244
|
+
now = Date.now(),
|
|
245
|
+
): number | null {
|
|
246
|
+
let nextDelay: number | null = null;
|
|
247
|
+
|
|
248
|
+
for (const recording of recordings) {
|
|
249
|
+
if (recording.status !== "ready") continue;
|
|
250
|
+
if (!isAutoTitleReplaceable(recording.title, recording.titleSource)) {
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
if (
|
|
254
|
+
recording.transcriptStatus !== "ready" ||
|
|
255
|
+
recording.transcriptHasText !== true ||
|
|
256
|
+
dispatched.has(`${recording.id}:fallback`)
|
|
257
|
+
) {
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const createdAt = new Date(recording.createdAt).getTime();
|
|
262
|
+
const delay = Number.isFinite(createdAt)
|
|
263
|
+
? Math.max(0, TWO_MINUTES_MS - (now - createdAt))
|
|
264
|
+
: 0;
|
|
265
|
+
nextDelay = nextDelay === null ? delay : Math.min(nextDelay, delay);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return nextDelay;
|
|
203
269
|
}
|
|
204
270
|
|
|
205
271
|
function buildRequestContext(rec: RecordingSummary, request: AiRequest) {
|
|
@@ -44,24 +44,13 @@ export interface ListRecordingsArgs {
|
|
|
44
44
|
offset?: number;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
function
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (!titleIsReplaceable) return false;
|
|
55
|
-
|
|
56
|
-
if (recording.transcriptStatus === "failed") return false;
|
|
57
|
-
if (recording.transcriptStatus === "ready") {
|
|
58
|
-
return recording.transcriptHasText === true;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return (
|
|
62
|
-
recording.transcriptStatus === "pending" ||
|
|
63
|
-
recording.transcriptStatus === "streaming"
|
|
64
|
-
);
|
|
47
|
+
export function recordingsRefetchInterval(
|
|
48
|
+
recordings: readonly RecordingSummary[] | undefined,
|
|
49
|
+
): number | false {
|
|
50
|
+
if (!recordings || recordings.length === 0) return false;
|
|
51
|
+
return recordings.some((recording) => isLiveRecordingUpload(recording))
|
|
52
|
+
? 3000
|
|
53
|
+
: false;
|
|
65
54
|
}
|
|
66
55
|
|
|
67
56
|
export function useRecordings(args: ListRecordingsArgs = {}) {
|
|
@@ -74,17 +63,15 @@ export function useRecordings(args: ListRecordingsArgs = {}) {
|
|
|
74
63
|
recordings: Array.isArray(data?.recordings) ? data.recordings : [],
|
|
75
64
|
};
|
|
76
65
|
},
|
|
77
|
-
// Keep a short poll while uploads/processors are active so the
|
|
78
|
-
// card does not get stuck if the global refresh signal is
|
|
79
|
-
//
|
|
66
|
+
// Keep a short poll only while uploads/processors are active so the
|
|
67
|
+
// library card does not get stuck if the global refresh signal is
|
|
68
|
+
// missed. Generated titles arrive through the shared DB sync transport;
|
|
69
|
+
// polling completed recordings forever is both redundant and expensive.
|
|
80
70
|
refetchInterval: (q) => {
|
|
81
71
|
const recs = (q.state.data as any)?.recordings as
|
|
82
72
|
| RecordingSummary[]
|
|
83
73
|
| undefined;
|
|
84
|
-
|
|
85
|
-
const pendingUpload = recs.some((rec) => isLiveRecordingUpload(rec));
|
|
86
|
-
const pendingTitle = recs.some(isAwaitingAutoTitle);
|
|
87
|
-
return pendingUpload || pendingTitle ? 3000 : false;
|
|
74
|
+
return recordingsRefetchInterval(recs);
|
|
88
75
|
},
|
|
89
76
|
},
|
|
90
77
|
);
|
|
@@ -734,7 +734,7 @@ export default function SettingsIndexRoute() {
|
|
|
734
734
|
builderStatus.loading ||
|
|
735
735
|
!builderConnect.hasFetchedStatus;
|
|
736
736
|
const s3Configured = activeProviderId === "s3";
|
|
737
|
-
const s3Collapsed =
|
|
737
|
+
const s3Collapsed = !s3Expanded;
|
|
738
738
|
const configuredApiKeyCount = AI_PROVIDER_FIELDS.filter(
|
|
739
739
|
(field) => apiKeyStatus[field.key],
|
|
740
740
|
).length;
|
|
@@ -908,7 +908,7 @@ export default function SettingsIndexRoute() {
|
|
|
908
908
|
</div>
|
|
909
909
|
|
|
910
910
|
<Collapsible
|
|
911
|
-
open={
|
|
911
|
+
open={!s3Collapsed}
|
|
912
912
|
onOpenChange={(open) => setS3Expanded(open)}
|
|
913
913
|
>
|
|
914
914
|
<div className="rounded-md border border-border">
|
|
@@ -939,26 +939,24 @@ export default function SettingsIndexRoute() {
|
|
|
939
939
|
: t("settings.s3OwnBucketDescription")}
|
|
940
940
|
</p>
|
|
941
941
|
</div>
|
|
942
|
-
|
|
943
|
-
<
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
</CollapsibleTrigger>
|
|
961
|
-
) : null}
|
|
942
|
+
<CollapsibleTrigger asChild>
|
|
943
|
+
<Button
|
|
944
|
+
type="button"
|
|
945
|
+
variant="ghost"
|
|
946
|
+
size="sm"
|
|
947
|
+
className="shrink-0"
|
|
948
|
+
>
|
|
949
|
+
{s3Collapsed
|
|
950
|
+
? t("settings.configureS3")
|
|
951
|
+
: t("settings.hideS3")}
|
|
952
|
+
<IconChevronDown
|
|
953
|
+
className={cn(
|
|
954
|
+
"h-4 w-4 transition-transform",
|
|
955
|
+
!s3Collapsed && "rotate-180",
|
|
956
|
+
)}
|
|
957
|
+
/>
|
|
958
|
+
</Button>
|
|
959
|
+
</CollapsibleTrigger>
|
|
962
960
|
</div>
|
|
963
961
|
|
|
964
962
|
<CollapsibleContent>
|
|
@@ -30,7 +30,7 @@ The agent modifies data in SQL, but the UI runs in the browser. SSE bridges same
|
|
|
30
30
|
useDbSync({ queryClient });
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
For each non-own event, `useDbSync` bumps a per-source counter (e.g. `dashboards`, `analyses`, `settings`, `action`) and invalidates a small fixed list of framework-internal prefixes (`["action"]`, `["app-state"]`, `["__set_url__"]`, etc.). It does **not** blanket-invalidate templates' own data queries
|
|
33
|
+
For each non-own event, `useDbSync` bumps a per-source counter (e.g. `dashboards`, `analyses`, `settings`, `action`) and invalidates a small fixed list of framework-internal prefixes (`["action"]`, `["app-state"]`, `["__set_url__"]`, etc.). It does **not** blanket-invalidate templates' own data queries — that caused request storms in production. A successful mutating action refreshes active `useActionQuery` observers under the `["action"]` prefix. Browser actions carry a tab id so their originating tab ignores the sync echo while other tabs refresh. Legacy apps can opt into broader compatibility with `actionInvalidatePredicate`, but first-party apps should use action-backed or source-versioned query keys. Idle fallback polling runs once per minute; active agent work temporarily uses the faster cadence.
|
|
34
34
|
|
|
35
35
|
3. **Templates fold per-source counters into their query keys.** This is the pattern that makes "agent writes show up without a manual refresh" reliable:
|
|
36
36
|
|
|
@@ -117,7 +117,7 @@ useQuery({
|
|
|
117
117
|
| UI not updating after agent writes | Is `useDbSync` called with the correct `queryClient`? Does the affected query have an active observer? |
|
|
118
118
|
| Poll endpoint not responding | Is `/_agent-native/poll` accessible? Is the server running? |
|
|
119
119
|
| SSE not connecting | Is `/_agent-native/events` accessible and authenticated? Polling should still keep the UI fresh as fallback. |
|
|
120
|
-
| High CPU / event storms |
|
|
120
|
+
| High CPU / event storms | Use targeted source keys, settle bursty list counters, and avoid broad action invalidation. |
|
|
121
121
|
|
|
122
122
|
## Jitter Prevention
|
|
123
123
|
|
|
@@ -30,7 +30,7 @@ The agent modifies data in SQL, but the UI runs in the browser. SSE bridges same
|
|
|
30
30
|
useDbSync({ queryClient });
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
For each non-own event, `useDbSync` bumps a per-source counter (e.g. `dashboards`, `analyses`, `settings`, `action`) and invalidates a small fixed list of framework-internal prefixes (`["action"]`, `["app-state"]`, `["__set_url__"]`, etc.). It does **not** blanket-invalidate templates' own data queries
|
|
33
|
+
For each non-own event, `useDbSync` bumps a per-source counter (e.g. `dashboards`, `analyses`, `settings`, `action`) and invalidates a small fixed list of framework-internal prefixes (`["action"]`, `["app-state"]`, `["__set_url__"]`, etc.). It does **not** blanket-invalidate templates' own data queries — that caused request storms in production. A successful mutating action refreshes active `useActionQuery` observers under the `["action"]` prefix. Browser actions carry a tab id so their originating tab ignores the sync echo while other tabs refresh. Legacy apps can opt into broader compatibility with `actionInvalidatePredicate`, but first-party apps should use action-backed or source-versioned query keys. Idle fallback polling runs once per minute; active agent work temporarily uses the faster cadence.
|
|
34
34
|
|
|
35
35
|
3. **Templates fold per-source counters into their query keys.** This is the pattern that makes "agent writes show up without a manual refresh" reliable:
|
|
36
36
|
|
|
@@ -117,7 +117,7 @@ useQuery({
|
|
|
117
117
|
| UI not updating after agent writes | Is `useDbSync` called with the correct `queryClient`? Does the affected query have an active observer? |
|
|
118
118
|
| Poll endpoint not responding | Is `/_agent-native/poll` accessible? Is the server running? |
|
|
119
119
|
| SSE not connecting | Is `/_agent-native/events` accessible and authenticated? Polling should still keep the UI fresh as fallback. |
|
|
120
|
-
| High CPU / event storms |
|
|
120
|
+
| High CPU / event storms | Use targeted source keys, settle bursty list counters, and avoid broad action invalidation. |
|
|
121
121
|
|
|
122
122
|
## Jitter Prevention
|
|
123
123
|
|
|
@@ -80,7 +80,7 @@ export function AppLayout({ children }: { children: React.ReactNode }) {
|
|
|
80
80
|
}).catch(() => {});
|
|
81
81
|
}, [location.pathname, isAnalytics, isSettings]);
|
|
82
82
|
|
|
83
|
-
//
|
|
83
|
+
// useDbSync invalidates this key when the agent writes a navigate command.
|
|
84
84
|
const { data: navCommand } = useQuery({
|
|
85
85
|
queryKey: ["navigate-command"],
|
|
86
86
|
queryFn: async () => {
|
|
@@ -94,7 +94,6 @@ export function AppLayout({ children }: { children: React.ReactNode }) {
|
|
|
94
94
|
return null;
|
|
95
95
|
}
|
|
96
96
|
},
|
|
97
|
-
refetchInterval: 2000,
|
|
98
97
|
});
|
|
99
98
|
|
|
100
99
|
useEffect(() => {
|
|
@@ -169,9 +169,6 @@ export default function Root() {
|
|
|
169
169
|
createAgentNativeQueryClient({
|
|
170
170
|
defaultOptions: {
|
|
171
171
|
queries: {
|
|
172
|
-
// Macros UI refetches on focus to pick up meals logged in other
|
|
173
|
-
// browser tabs or mobile, which don't always arrive via DB sync.
|
|
174
|
-
refetchOnWindowFocus: true,
|
|
175
172
|
// Flat retry: macros data errors are usually transient network
|
|
176
173
|
// issues, not auth failures, so a flat count is sufficient.
|
|
177
174
|
retry: 1,
|
|
@@ -30,7 +30,7 @@ The agent modifies data in SQL, but the UI runs in the browser. SSE bridges same
|
|
|
30
30
|
useDbSync({ queryClient });
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
For each non-own event, `useDbSync` bumps a per-source counter (e.g. `dashboards`, `analyses`, `settings`, `action`) and invalidates a small fixed list of framework-internal prefixes (`["action"]`, `["app-state"]`, `["__set_url__"]`, etc.). It does **not** blanket-invalidate templates' own data queries
|
|
33
|
+
For each non-own event, `useDbSync` bumps a per-source counter (e.g. `dashboards`, `analyses`, `settings`, `action`) and invalidates a small fixed list of framework-internal prefixes (`["action"]`, `["app-state"]`, `["__set_url__"]`, etc.). It does **not** blanket-invalidate templates' own data queries — that caused request storms in production. A successful mutating action refreshes active `useActionQuery` observers under the `["action"]` prefix. Browser actions carry a tab id so their originating tab ignores the sync echo while other tabs refresh. Legacy apps can opt into broader compatibility with `actionInvalidatePredicate`, but first-party apps should use action-backed or source-versioned query keys. Idle fallback polling runs once per minute; active agent work temporarily uses the faster cadence.
|
|
34
34
|
|
|
35
35
|
3. **Templates fold per-source counters into their query keys.** This is the pattern that makes "agent writes show up without a manual refresh" reliable:
|
|
36
36
|
|
|
@@ -117,7 +117,7 @@ useQuery({
|
|
|
117
117
|
| UI not updating after agent writes | Is `useDbSync` called with the correct `queryClient`? Does the affected query have an active observer? |
|
|
118
118
|
| Poll endpoint not responding | Is `/_agent-native/poll` accessible? Is the server running? |
|
|
119
119
|
| SSE not connecting | Is `/_agent-native/events` accessible and authenticated? Polling should still keep the UI fresh as fallback. |
|
|
120
|
-
| High CPU / event storms |
|
|
120
|
+
| High CPU / event storms | Use targeted source keys, settle bursty list counters, and avoid broad action invalidation. |
|
|
121
121
|
|
|
122
122
|
## Jitter Prevention
|
|
123
123
|
|
|
@@ -419,18 +419,7 @@ function DbSyncSetup() {
|
|
|
419
419
|
const MAIL_TOASTER = <Toaster richColors position="bottom-left" />;
|
|
420
420
|
|
|
421
421
|
export default function Root() {
|
|
422
|
-
const [queryClient] = useState(() =>
|
|
423
|
-
createAgentNativeQueryClient({
|
|
424
|
-
defaultOptions: {
|
|
425
|
-
queries: {
|
|
426
|
-
// Mail's VisibilityRefresh handles the focus-based refresh with a
|
|
427
|
-
// 60 s throttle, so we also want React Query's focus refetch to
|
|
428
|
-
// fire for other query keys (labels, settings, etc.).
|
|
429
|
-
refetchOnWindowFocus: true,
|
|
430
|
-
},
|
|
431
|
-
},
|
|
432
|
-
}),
|
|
433
|
-
);
|
|
422
|
+
const [queryClient] = useState(() => createAgentNativeQueryClient());
|
|
434
423
|
return (
|
|
435
424
|
<AppToolkitProvider>
|
|
436
425
|
<AppProviders
|
|
@@ -30,7 +30,7 @@ The agent modifies data in SQL, but the UI runs in the browser. SSE bridges same
|
|
|
30
30
|
useDbSync({ queryClient });
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
For each non-own event, `useDbSync` bumps a per-source counter (e.g. `dashboards`, `analyses`, `settings`, `action`) and invalidates a small fixed list of framework-internal prefixes (`["action"]`, `["app-state"]`, `["__set_url__"]`, etc.). It does **not** blanket-invalidate templates' own data queries
|
|
33
|
+
For each non-own event, `useDbSync` bumps a per-source counter (e.g. `dashboards`, `analyses`, `settings`, `action`) and invalidates a small fixed list of framework-internal prefixes (`["action"]`, `["app-state"]`, `["__set_url__"]`, etc.). It does **not** blanket-invalidate templates' own data queries — that caused request storms in production. A successful mutating action refreshes active `useActionQuery` observers under the `["action"]` prefix. Browser actions carry a tab id so their originating tab ignores the sync echo while other tabs refresh. Legacy apps can opt into broader compatibility with `actionInvalidatePredicate`, but first-party apps should use action-backed or source-versioned query keys. Idle fallback polling runs once per minute; active agent work temporarily uses the faster cadence.
|
|
34
34
|
|
|
35
35
|
3. **Templates fold per-source counters into their query keys.** This is the pattern that makes "agent writes show up without a manual refresh" reliable:
|
|
36
36
|
|
|
@@ -117,7 +117,7 @@ useQuery({
|
|
|
117
117
|
| UI not updating after agent writes | Is `useDbSync` called with the correct `queryClient`? Does the affected query have an active observer? |
|
|
118
118
|
| Poll endpoint not responding | Is `/_agent-native/poll` accessible? Is the server running? |
|
|
119
119
|
| SSE not connecting | Is `/_agent-native/events` accessible and authenticated? Polling should still keep the UI fresh as fallback. |
|
|
120
|
-
| High CPU / event storms |
|
|
120
|
+
| High CPU / event storms | Use targeted source keys, settle bursty list counters, and avoid broad action invalidation. |
|
|
121
121
|
|
|
122
122
|
## Jitter Prevention
|
|
123
123
|
|
|
@@ -55,7 +55,10 @@ export function useNavigationState() {
|
|
|
55
55
|
}).catch(() => {});
|
|
56
56
|
}, [location.pathname, location.search]);
|
|
57
57
|
|
|
58
|
-
// Listen for navigate commands from agent
|
|
58
|
+
// Listen for one-shot navigate commands from the agent. useDbSync
|
|
59
|
+
// invalidates this exact key when the shared SSE/poll transport receives an
|
|
60
|
+
// app-state:navigate event, so this stays idle between real commands instead
|
|
61
|
+
// of charging the host for a request every two seconds.
|
|
59
62
|
const { data: navCommand } = useQuery({
|
|
60
63
|
queryKey: ["navigate-command"],
|
|
61
64
|
queryFn: async () => {
|
|
@@ -70,7 +73,7 @@ export function useNavigationState() {
|
|
|
70
73
|
}
|
|
71
74
|
return null;
|
|
72
75
|
},
|
|
73
|
-
|
|
76
|
+
retry: false,
|
|
74
77
|
structuralSharing: false,
|
|
75
78
|
});
|
|
76
79
|
|
|
@@ -17,7 +17,6 @@ import type {
|
|
|
17
17
|
PlanVersionListResponse,
|
|
18
18
|
} from "@shared/types";
|
|
19
19
|
import { useQueryClient, type UseQueryOptions } from "@tanstack/react-query";
|
|
20
|
-
import type { RefObject } from "react";
|
|
21
20
|
import { toast } from "sonner";
|
|
22
21
|
|
|
23
22
|
import type { PlanMdxFolder } from "@/lib/desktop-plan-files";
|
|
@@ -337,23 +336,17 @@ export function localPlanBundleQueryKey(slug: string, path?: string | null) {
|
|
|
337
336
|
] as const;
|
|
338
337
|
}
|
|
339
338
|
|
|
340
|
-
export function usePlan(
|
|
341
|
-
id?: string,
|
|
342
|
-
pausePollRef?: RefObject<boolean> | { current: boolean },
|
|
343
|
-
) {
|
|
339
|
+
export function usePlan(id?: string) {
|
|
344
340
|
return useActionQuery<PlanBundle & { html?: string }>(
|
|
345
341
|
"get-visual-plan",
|
|
346
342
|
planBundleQueryParams(id ?? ""),
|
|
347
343
|
{
|
|
348
344
|
enabled: !!id,
|
|
349
|
-
//
|
|
350
|
-
//
|
|
351
|
-
// the
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
if (pausePollRef?.current) return false;
|
|
355
|
-
return 3_000;
|
|
356
|
-
},
|
|
345
|
+
// Mutating actions and collaboration writes already arrive through the
|
|
346
|
+
// shared useDbSync SSE/poll transport, which invalidates active action
|
|
347
|
+
// reads. Keep the last bundle visible during that targeted refresh and
|
|
348
|
+
// avoid repeatedly downloading the full plan while nothing changed.
|
|
349
|
+
placeholderData: (previous) => previous,
|
|
357
350
|
},
|
|
358
351
|
);
|
|
359
352
|
}
|
|
@@ -2018,7 +2018,6 @@ export function PlansPage({ localPlanSlug }: { localPlanSlug?: string } = {}) {
|
|
|
2018
2018
|
// Ref that signals the 3-second poll to pause while a comment mutation is
|
|
2019
2019
|
// in-flight. Prevents poll-driven cache replacement from evicting optimistic
|
|
2020
2020
|
// comments before the server write commits (Issue 4a).
|
|
2021
|
-
const commentMutationPendingRef = useRef(false);
|
|
2022
2021
|
const { session, isLoading: sessionLoading } = useSession();
|
|
2023
2022
|
const localPlanMode = Boolean(localPlanSlug);
|
|
2024
2023
|
const routeSearchParams = useMemo(
|
|
@@ -2217,10 +2216,7 @@ export function PlansPage({ localPlanSlug }: { localPlanSlug?: string } = {}) {
|
|
|
2217
2216
|
const immersiveReader = Boolean(
|
|
2218
2217
|
selectedId && (planFullscreen || prototypeOnly),
|
|
2219
2218
|
);
|
|
2220
|
-
const planQuery = usePlan(
|
|
2221
|
-
localPlanMode ? undefined : selectedId,
|
|
2222
|
-
commentMutationPendingRef,
|
|
2223
|
-
);
|
|
2219
|
+
const planQuery = usePlan(localPlanMode ? undefined : selectedId);
|
|
2224
2220
|
const bundle = localPlanMode ? localPlanData : planQuery.data;
|
|
2225
2221
|
const localPlanBundle =
|
|
2226
2222
|
localPlanMode && bundle && "localOnly" in bundle
|
|
@@ -4550,8 +4546,7 @@ export function PlansPage({ localPlanSlug }: { localPlanSlug?: string } = {}) {
|
|
|
4550
4546
|
};
|
|
4551
4547
|
clearPendingDocumentRestore();
|
|
4552
4548
|
pendingDocumentRestoreRef.current = documentStateRef.current;
|
|
4553
|
-
|
|
4554
|
-
// Await the cancel so an in-flight 3s poll can't resolve *after* our
|
|
4549
|
+
// Await the cancel so an in-flight sync refresh can't resolve *after* our
|
|
4555
4550
|
// optimistic write and revert it (the "comment lagged / didn't stick"
|
|
4556
4551
|
// symptom). cancelQueries reverts outstanding fetches before we patch.
|
|
4557
4552
|
await queryClient.cancelQueries({ queryKey: selectedPlanQueryKey });
|
|
@@ -4588,9 +4583,6 @@ export function PlansPage({ localPlanSlug }: { localPlanSlug?: string } = {}) {
|
|
|
4588
4583
|
setInlineCommentPosition(
|
|
4589
4584
|
getPositionFromAnchor(anchor) ?? capturedPosition,
|
|
4590
4585
|
);
|
|
4591
|
-
})
|
|
4592
|
-
.finally(() => {
|
|
4593
|
-
commentMutationPendingRef.current = false;
|
|
4594
4586
|
});
|
|
4595
4587
|
};
|
|
4596
4588
|
|
|
@@ -4606,7 +4598,6 @@ export function PlansPage({ localPlanSlug }: { localPlanSlug?: string } = {}) {
|
|
|
4606
4598
|
annotation.anchor.resolutionTarget,
|
|
4607
4599
|
),
|
|
4608
4600
|
};
|
|
4609
|
-
commentMutationPendingRef.current = true;
|
|
4610
4601
|
void writeComments(
|
|
4611
4602
|
[
|
|
4612
4603
|
{
|
|
@@ -4629,9 +4620,6 @@ export function PlansPage({ localPlanSlug }: { localPlanSlug?: string } = {}) {
|
|
|
4629
4620
|
})
|
|
4630
4621
|
.catch(() => {
|
|
4631
4622
|
// The mutation hook surfaces the failure toast; just clear pending.
|
|
4632
|
-
})
|
|
4633
|
-
.finally(() => {
|
|
4634
|
-
commentMutationPendingRef.current = false;
|
|
4635
4623
|
});
|
|
4636
4624
|
};
|
|
4637
4625
|
|
|
@@ -4669,8 +4657,7 @@ export function PlansPage({ localPlanSlug }: { localPlanSlug?: string } = {}) {
|
|
|
4669
4657
|
createdAt: now,
|
|
4670
4658
|
updatedAt: now,
|
|
4671
4659
|
};
|
|
4672
|
-
|
|
4673
|
-
// Await the cancel so an in-flight 3s poll can't resolve *after* our
|
|
4660
|
+
// Await the cancel so an in-flight sync refresh can't resolve *after* our
|
|
4674
4661
|
// optimistic write and revert it (the "comment lagged / didn't stick"
|
|
4675
4662
|
// symptom). cancelQueries reverts outstanding fetches before we patch.
|
|
4676
4663
|
await queryClient.cancelQueries({ queryKey: selectedPlanQueryKey });
|
|
@@ -4709,8 +4696,6 @@ export function PlansPage({ localPlanSlug }: { localPlanSlug?: string } = {}) {
|
|
|
4709
4696
|
current ? removePlanCommentFromBundle(current, replyId) : current,
|
|
4710
4697
|
);
|
|
4711
4698
|
throw new Error("Could not send reply. Try again.");
|
|
4712
|
-
} finally {
|
|
4713
|
-
commentMutationPendingRef.current = false;
|
|
4714
4699
|
}
|
|
4715
4700
|
};
|
|
4716
4701
|
|
|
@@ -4729,8 +4714,7 @@ export function PlansPage({ localPlanSlug }: { localPlanSlug?: string } = {}) {
|
|
|
4729
4714
|
// popover update without waiting for the server round-trip (Issue 3).
|
|
4730
4715
|
const prevBundle =
|
|
4731
4716
|
queryClient.getQueryData<PlanBundleWithHtml>(selectedPlanQueryKey);
|
|
4732
|
-
|
|
4733
|
-
// Await the cancel so an in-flight 3s poll can't resolve *after* our
|
|
4717
|
+
// Await the cancel so an in-flight sync refresh can't resolve *after* our
|
|
4734
4718
|
// optimistic write and revert it (the "comment lagged / didn't stick"
|
|
4735
4719
|
// symptom). cancelQueries reverts outstanding fetches before we patch.
|
|
4736
4720
|
await queryClient.cancelQueries({ queryKey: selectedPlanQueryKey });
|
|
@@ -4777,9 +4761,6 @@ export function PlansPage({ localPlanSlug }: { localPlanSlug?: string } = {}) {
|
|
|
4777
4761
|
if (prevBundle !== undefined) {
|
|
4778
4762
|
queryClient.setQueryData(selectedPlanQueryKey, prevBundle);
|
|
4779
4763
|
}
|
|
4780
|
-
})
|
|
4781
|
-
.finally(() => {
|
|
4782
|
-
commentMutationPendingRef.current = false;
|
|
4783
4764
|
});
|
|
4784
4765
|
};
|
|
4785
4766
|
|
|
@@ -4802,8 +4783,7 @@ export function PlansPage({ localPlanSlug }: { localPlanSlug?: string } = {}) {
|
|
|
4802
4783
|
const prevBundle =
|
|
4803
4784
|
queryClient.getQueryData<PlanBundleWithHtml>(selectedPlanQueryKey);
|
|
4804
4785
|
const commentId = request.commentId;
|
|
4805
|
-
|
|
4806
|
-
// Await the cancel so an in-flight 3s poll can't resolve *after* our
|
|
4786
|
+
// Await the cancel so an in-flight sync refresh can't resolve *after* our
|
|
4807
4787
|
// optimistic write and revert it (the "comment lagged / didn't stick"
|
|
4808
4788
|
// symptom). cancelQueries reverts outstanding fetches before we patch.
|
|
4809
4789
|
await queryClient.cancelQueries({ queryKey: selectedPlanQueryKey });
|
|
@@ -4826,8 +4806,6 @@ export function PlansPage({ localPlanSlug }: { localPlanSlug?: string } = {}) {
|
|
|
4826
4806
|
queryClient.setQueryData(selectedPlanQueryKey, prevBundle);
|
|
4827
4807
|
}
|
|
4828
4808
|
setDeleteCommentRequest(request);
|
|
4829
|
-
} finally {
|
|
4830
|
-
commentMutationPendingRef.current = false;
|
|
4831
4809
|
}
|
|
4832
4810
|
};
|
|
4833
4811
|
|
|
@@ -336,7 +336,10 @@ function ThreadCard({
|
|
|
336
336
|
<TooltipTrigger asChild>
|
|
337
337
|
<button
|
|
338
338
|
onClick={() =>
|
|
339
|
-
resolveComment.mutate({
|
|
339
|
+
resolveComment.mutate({
|
|
340
|
+
id: rootComment.id,
|
|
341
|
+
resolved: true,
|
|
342
|
+
})
|
|
340
343
|
}
|
|
341
344
|
className="p-0.5 rounded text-muted-foreground hover:text-green-400"
|
|
342
345
|
>
|