@agent-native/core 0.120.2 → 0.120.4
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 +3 -3
- package/corpus/core/CHANGELOG.md +28 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/context-xray-local.ts +1 -1
- package/corpus/core/src/cli/index.ts +63 -18
- package/corpus/core/src/cli/skills.ts +5 -0
- package/corpus/core/src/cli/telemetry.ts +118 -1
- package/corpus/core/src/client/AgentPanel.tsx +10 -0
- package/corpus/core/src/client/CommandMenu.tsx +13 -1
- package/corpus/core/src/client/agent-page/AgentTabsPage.tsx +39 -4
- package/corpus/core/src/client/analytics.ts +13 -0
- package/corpus/core/src/client/error-capture.ts +6 -1
- package/corpus/core/src/client/extensions/ExtensionsListPage.tsx +12 -5
- package/corpus/core/src/client/extensions/agent-native-extension-runtime.ts +1 -1
- package/corpus/core/src/client/navigation/index.ts +2 -0
- package/corpus/core/src/client/org/OrgSwitcher.tsx +14 -32
- package/corpus/core/src/client/settings/ExtensionsSettingsContent.tsx +1 -1
- package/corpus/core/src/extensions/theme.ts +1 -1
- package/corpus/core/src/localization/default-messages.ts +2 -0
- package/corpus/core/src/server/core-routes-plugin.ts +10 -1
- package/corpus/core/src/server/framework-request-handler.ts +17 -26
- package/corpus/core/src/styles/agent-native.css +13 -0
- package/corpus/core/src/templates/chat/app/components/layout/Layout.tsx +3 -0
- package/corpus/core/src/templates/chat/app/components/layout/Sidebar.tsx +78 -45
- package/corpus/core/src/templates/chat/app/global.css +1 -1
- package/corpus/core/src/templates/default/app/global.css +1 -1
- package/corpus/core/src/tracking/error-capture.ts +151 -0
- package/corpus/core/src/tracking/index.ts +5 -0
- package/corpus/templates/analytics/app/components/dashboard/CumulativeNetChart.tsx +1 -1
- package/corpus/templates/analytics/app/components/dashboard/DashboardHistoryPanel.tsx +1 -4
- package/corpus/templates/analytics/app/components/dashboard/DashboardPanelSkeleton.tsx +1 -1
- package/corpus/templates/analytics/app/components/dashboard/DataTable.tsx +1 -1
- package/corpus/templates/analytics/app/components/dashboard/DateRangePicker.tsx +2 -2
- package/corpus/templates/analytics/app/components/dashboard/MetricCard.tsx +1 -1
- package/corpus/templates/analytics/app/components/dashboard/RecentActivity.tsx +1 -1
- package/corpus/templates/analytics/app/components/dashboard/RevenueChart.tsx +1 -1
- package/corpus/templates/analytics/app/components/dashboard/RevenueComparisonChart.tsx +1 -1
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +41 -9
- package/corpus/templates/analytics/app/components/dashboard/StatsCard.tsx +1 -1
- package/corpus/templates/analytics/app/components/dashboard/TimeSeriesChart.tsx +1 -1
- package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +2 -0
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +28 -14
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +152 -124
- package/corpus/templates/analytics/app/global.css +5 -5
- package/corpus/templates/analytics/app/lib/dashboard-report-capture.ts +42 -0
- package/corpus/templates/analytics/app/pages/adhoc/_shared/KpiChart.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/explorer/components/ChartTypePicker.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/explorer/components/DateRangePicker.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/explorer/components/EventRow.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/explorer/components/SqlPreview.tsx +2 -2
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/DashboardFilterBar.tsx +3 -3
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/EmailReportDialog.tsx +2 -2
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +162 -51
- package/corpus/templates/analytics/app/root.tsx +28 -0
- package/corpus/templates/analytics/changelog/2026-07-24-analytics-charts-now-explain-slow-loads-and-offer-a-direct-r.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-24-daily-dashboard-emails-now-load-signed-reports-more-reliably.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-24-manage-agent-is-clearly-marked-as-a-link-to-its-dedicated-pa.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +1 -1
- package/corpus/templates/analytics/server/db/schema.ts +5 -0
- package/corpus/templates/analytics/server/jobs/dashboard-report.ts +21 -0
- package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +11 -0
- package/corpus/templates/analytics/server/lib/dashboard-report-subscriptions.ts +54 -0
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +235 -26
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +4 -1
- package/corpus/templates/analytics/server/plugins/db.ts +9 -0
- package/corpus/templates/analytics/shared/dashboard-report-timeouts.ts +1 -1
- package/corpus/templates/assets/app/components/asset/AssetPreviewDialog.tsx +376 -0
- package/corpus/templates/assets/app/components/create/RecentDraftsSection.tsx +14 -4
- package/corpus/templates/assets/app/components/layout/Layout.tsx +5 -0
- package/corpus/templates/assets/app/components/layout/Sidebar.tsx +118 -74
- package/corpus/templates/assets/app/global.css +1 -1
- package/corpus/templates/assets/app/i18n-data.ts +10 -0
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +99 -10
- package/corpus/templates/assets/app/routes/library.tsx +21 -198
- package/corpus/templates/assets/changelog/2026-07-21-unified-asset-preview-click-any-asset-to-open-one-preview-wi.md +6 -0
- package/corpus/templates/assets/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/assets/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/brain/app/components/layout/Layout.tsx +3 -0
- package/corpus/templates/brain/app/components/layout/Sidebar.tsx +76 -43
- package/corpus/templates/brain/app/global.css +1 -1
- package/corpus/templates/brain/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/brain/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +67 -52
- package/corpus/templates/calendar/app/global.css +1 -1
- package/corpus/templates/calendar/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/calendar/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/chat/app/components/layout/Layout.tsx +3 -0
- package/corpus/templates/chat/app/components/layout/Sidebar.tsx +78 -45
- package/corpus/templates/chat/app/global.css +1 -1
- package/corpus/templates/chat/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/chat/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/clips/app/components/library/library-layout.tsx +65 -31
- package/corpus/templates/clips/app/components/library/organization-switcher.tsx +2 -2
- package/corpus/templates/clips/app/components/player/video-player.tsx +16 -1
- package/corpus/templates/clips/app/global.css +1 -1
- package/corpus/templates/clips/app/hooks/use-view-tracking.ts +55 -12
- package/corpus/templates/clips/app/routes/embed.$shareId.tsx +6 -5
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +7 -7
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +6 -5
- package/corpus/templates/clips/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/clips/server/plugins/db.ts +113 -68
- package/corpus/templates/content/actions/update-document.ts +145 -91
- package/corpus/templates/content/app/components/editor/CommentComposer.tsx +4 -1
- package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +366 -164
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +51 -28
- package/corpus/templates/content/app/components/editor/database/sidebar.tsx +19 -8
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +66 -42
- package/corpus/templates/content/app/global.css +1 -1
- package/corpus/templates/content/app/i18n-data.ts +15 -0
- package/corpus/templates/content/changelog/2026-07-23-shared-pages-now-keep-durable-content-visible-and-comment-th.md +6 -0
- package/corpus/templates/content/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/content/changelog/2026-07-24-comment-drafts-now-stay-open-when-saving-fails-and-long-live.md +6 -0
- package/corpus/templates/content/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/design/actions/update-file.ts +85 -18
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +65 -30
- package/corpus/templates/design/app/entry.client.tsx +14 -1
- package/corpus/templates/design/app/global.css +1 -1
- package/corpus/templates/design/app/lib/design-save-outbox.ts +39 -0
- package/corpus/templates/design/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/design/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/dispatch/app/global.css +1 -1
- package/corpus/templates/dispatch/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/forms/app/components/layout/Layout.tsx +3 -0
- package/corpus/templates/forms/app/components/layout/Sidebar.tsx +72 -37
- package/corpus/templates/forms/app/global.css +1 -1
- package/corpus/templates/forms/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/forms/changelog/2026-07-24-forms-sidebar-actions-now-keep-search-language-feedback-and-.md +6 -0
- package/corpus/templates/forms/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/macros/app/components/DailyProgress.tsx +5 -5
- package/corpus/templates/macros/app/components/ExerciseCard.tsx +2 -2
- package/corpus/templates/macros/app/components/MealCard.tsx +2 -2
- package/corpus/templates/macros/app/components/WeightCard.tsx +2 -2
- package/corpus/templates/macros/app/components/layout/AppLayout.tsx +56 -29
- package/corpus/templates/macros/app/components/ui/button.tsx +1 -2
- package/corpus/templates/macros/app/components/ui/card.tsx +1 -1
- package/corpus/templates/macros/app/global.css +1 -1
- package/corpus/templates/macros/app/routes/_index.tsx +1 -1
- package/corpus/templates/macros/app/routes/analytics.tsx +4 -7
- package/corpus/templates/macros/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/macros/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +125 -37
- package/corpus/templates/mail/app/global.css +1 -1
- package/corpus/templates/mail/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/mail/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/plan/app/components/layout/Layout.tsx +3 -0
- package/corpus/templates/plan/app/components/layout/Sidebar.tsx +90 -60
- package/corpus/templates/plan/app/global.css +1 -1
- package/corpus/templates/plan/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/plan/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/slides/app/components/layout/Sidebar.tsx +69 -32
- package/corpus/templates/slides/app/global.css +1 -1
- package/corpus/templates/slides/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/tasks/app/global.css +1 -1
- package/corpus/templates/tasks/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/toolkit/CHANGELOG.md +14 -0
- package/corpus/toolkit/README.md +8 -1
- package/corpus/toolkit/agent-native.eject.json +1 -1
- package/corpus/toolkit/package.json +1 -1
- package/corpus/toolkit/src/app-shell/index.ts +1 -0
- package/corpus/toolkit/src/app-shell/sidebar-footer-actions.tsx +48 -0
- package/corpus/toolkit/src/chat-history.css +52 -1
- package/corpus/toolkit/src/design-system/default-adapter.tsx +1 -1
- package/corpus/toolkit/src/editor/useCollabReconcile.ts +83 -15
- package/corpus/toolkit/src/ui/button.tsx +1 -2
- package/corpus/toolkit/src/ui/card.tsx +1 -1
- package/corpus/toolkit/src/ui/table.tsx +4 -11
- package/dist/cli/context-xray-local.js +1 -1
- package/dist/cli/context-xray-local.js.map +1 -1
- package/dist/cli/index.js +56 -17
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +5 -0
- package/dist/cli/skills.js.map +1 -1
- package/dist/cli/telemetry.d.ts +7 -0
- package/dist/cli/telemetry.d.ts.map +1 -1
- package/dist/cli/telemetry.js +91 -1
- package/dist/cli/telemetry.js.map +1 -1
- package/dist/client/AgentPanel.d.ts +7 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +2 -2
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/CommandMenu.d.ts +2 -0
- package/dist/client/CommandMenu.d.ts.map +1 -1
- package/dist/client/CommandMenu.js +12 -1
- package/dist/client/CommandMenu.js.map +1 -1
- package/dist/client/agent-page/AgentTabsPage.d.ts +4 -0
- package/dist/client/agent-page/AgentTabsPage.d.ts.map +1 -1
- package/dist/client/agent-page/AgentTabsPage.js +8 -4
- package/dist/client/agent-page/AgentTabsPage.js.map +1 -1
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +7 -0
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/error-capture.d.ts.map +1 -1
- package/dist/client/error-capture.js +8 -1
- package/dist/client/error-capture.js.map +1 -1
- package/dist/client/extensions/ExtensionsListPage.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionsListPage.js +2 -2
- package/dist/client/extensions/ExtensionsListPage.js.map +1 -1
- package/dist/client/extensions/agent-native-extension-runtime.js +1 -1
- package/dist/client/extensions/agent-native-extension-runtime.js.map +1 -1
- package/dist/client/navigation/index.d.ts +1 -1
- package/dist/client/navigation/index.d.ts.map +1 -1
- package/dist/client/navigation/index.js +1 -1
- package/dist/client/navigation/index.js.map +1 -1
- package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
- package/dist/client/org/OrgSwitcher.js +9 -13
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/settings/ExtensionsSettingsContent.js +1 -1
- package/dist/client/settings/ExtensionsSettingsContent.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/extensions/theme.js +1 -1
- package/dist/extensions/theme.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/localization/default-messages.d.ts +2 -0
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +2 -0
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/provider-api/actions/provider-api.d.ts +2 -2
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +8 -1
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/framework-request-handler.d.ts.map +1 -1
- package/dist/server/framework-request-handler.js +17 -27
- package/dist/server/framework-request-handler.js.map +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/styles/agent-native.css +13 -0
- package/dist/templates/chat/app/components/layout/Layout.tsx +3 -0
- package/dist/templates/chat/app/components/layout/Sidebar.tsx +78 -45
- package/dist/templates/chat/app/global.css +1 -1
- package/dist/templates/chat/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/dist/templates/chat/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/dist/templates/default/app/global.css +1 -1
- package/dist/tracking/error-capture.d.ts +14 -0
- package/dist/tracking/error-capture.d.ts.map +1 -0
- package/dist/tracking/error-capture.js +113 -0
- package/dist/tracking/error-capture.js.map +1 -0
- package/dist/tracking/index.d.ts +1 -0
- package/dist/tracking/index.d.ts.map +1 -1
- package/dist/tracking/index.js +1 -0
- package/dist/tracking/index.js.map +1 -1
- package/package.json +2 -2
- package/src/cli/context-xray-local.ts +1 -1
- package/src/cli/index.ts +63 -18
- package/src/cli/skills.ts +5 -0
- package/src/cli/telemetry.ts +118 -1
- package/src/client/AgentPanel.tsx +10 -0
- package/src/client/CommandMenu.tsx +13 -1
- package/src/client/agent-page/AgentTabsPage.tsx +39 -4
- package/src/client/analytics.ts +13 -0
- package/src/client/error-capture.ts +6 -1
- package/src/client/extensions/ExtensionsListPage.tsx +12 -5
- package/src/client/extensions/agent-native-extension-runtime.ts +1 -1
- package/src/client/navigation/index.ts +2 -0
- package/src/client/org/OrgSwitcher.tsx +14 -32
- package/src/client/settings/ExtensionsSettingsContent.tsx +1 -1
- package/src/extensions/theme.ts +1 -1
- package/src/localization/default-messages.ts +2 -0
- package/src/server/core-routes-plugin.ts +10 -1
- package/src/server/framework-request-handler.ts +17 -26
- package/src/styles/agent-native.css +13 -0
- package/src/templates/chat/app/components/layout/Layout.tsx +3 -0
- package/src/templates/chat/app/components/layout/Sidebar.tsx +78 -45
- package/src/templates/chat/app/global.css +1 -1
- package/src/templates/chat/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/src/templates/chat/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/src/templates/default/app/global.css +1 -1
- package/src/tracking/error-capture.ts +151 -0
- package/src/tracking/index.ts +5 -0
|
@@ -644,16 +644,8 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
644
644
|
}
|
|
645
645
|
: undefined;
|
|
646
646
|
|
|
647
|
-
//
|
|
648
|
-
//
|
|
649
|
-
// cursors, and presence (Google-Docs style) instead of a lagging SQL snapshot.
|
|
650
|
-
// The server enforces the split — collab READ routes (state / awareness GET /
|
|
651
|
-
// users) require viewer access, WRITE routes (update) require editor — so a
|
|
652
|
-
// viewer's client can subscribe but never push. The editor stays non-editable
|
|
653
|
-
// for viewers (see `editable={canEdit}` below), and VisualEditor additionally
|
|
654
|
-
// neutralizes every local Y.Doc mutation for viewers (no seed, no reconcile
|
|
655
|
-
// apply) so a read-only client can never originate a rejected `/update` POST.
|
|
656
|
-
// Local-file documents are still excluded (they have no SQL-backed collab doc).
|
|
647
|
+
// All SQL-backed readers subscribe for presence. Only editors bind the body
|
|
648
|
+
// to Yjs; viewers render canonical SQL so missing collab state cannot hide it.
|
|
657
649
|
const collabEnabled = !isLocalFileDocument;
|
|
658
650
|
const {
|
|
659
651
|
ydoc,
|
|
@@ -671,6 +663,12 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
671
663
|
const bodyHydrationPending = documentBodyHydrationIsPending(document);
|
|
672
664
|
const editorCanEdit =
|
|
673
665
|
canEdit && !bodyHydrationPending && (isLocalFileDocument || !collabLoading);
|
|
666
|
+
// Bind an editor's stable Y.Doc on its first mount, even while the initial
|
|
667
|
+
// state is loading. Editability remains gated by `editorCanEdit`, and the
|
|
668
|
+
// reconcile hook remains gated by `collabSynced`; keeping the Y.Doc binding
|
|
669
|
+
// stable avoids a snapshot -> collab remount that can seed the same SQL body
|
|
670
|
+
// beside freshly projected persisted CRDT content.
|
|
671
|
+
const collabEditorEnabled = collabEnabled && canEdit && !bodyHydrationPending;
|
|
674
672
|
canEditRef.current = editorCanEdit;
|
|
675
673
|
|
|
676
674
|
// Viewers intentionally join awareness so they receive live cursors, but
|
|
@@ -900,6 +898,12 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
900
898
|
},
|
|
901
899
|
[document, documentId, queryClient, updateDocument],
|
|
902
900
|
);
|
|
901
|
+
// The document query can refresh its object identity without changing the
|
|
902
|
+
// flush request itself. Keep the latest save function behind a ref so those
|
|
903
|
+
// routine refreshes do not restart the one-shot flush reader and flood the
|
|
904
|
+
// browser with duplicate application-state requests.
|
|
905
|
+
const persistDocumentUpdatesRef = useRef(persistDocumentUpdates);
|
|
906
|
+
persistDocumentUpdatesRef.current = persistDocumentUpdates;
|
|
903
907
|
|
|
904
908
|
const saveDocumentImmediately = useCallback(
|
|
905
909
|
async (
|
|
@@ -1216,7 +1220,7 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1216
1220
|
}
|
|
1217
1221
|
try {
|
|
1218
1222
|
if (Object.keys(updates).length > 0) {
|
|
1219
|
-
const saved = await
|
|
1223
|
+
const saved = await persistDocumentUpdatesRef.current(updates);
|
|
1220
1224
|
if (isDocumentUpdateConflict(saved)) {
|
|
1221
1225
|
// Do not acknowledge a CAS loss as a successful flush. The
|
|
1222
1226
|
// requester must stop instead of pushing/replacing stale SQL.
|
|
@@ -1290,7 +1294,6 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1290
1294
|
flushRequestKey,
|
|
1291
1295
|
flushRequestWake,
|
|
1292
1296
|
isLocalFileDocument,
|
|
1293
|
-
persistDocumentUpdates,
|
|
1294
1297
|
t,
|
|
1295
1298
|
]);
|
|
1296
1299
|
|
|
@@ -1372,6 +1375,24 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1372
1375
|
setHoveredThreadId(null);
|
|
1373
1376
|
}, []);
|
|
1374
1377
|
|
|
1378
|
+
const activateCommentThread = useCallback((threadId: string) => {
|
|
1379
|
+
setPendingComment(null);
|
|
1380
|
+
setHoveredThreadId(null);
|
|
1381
|
+
setSelectedThreadId(threadId);
|
|
1382
|
+
setUtilityPanel("comments");
|
|
1383
|
+
}, []);
|
|
1384
|
+
|
|
1385
|
+
const handleUtilityPanelChange = useCallback(
|
|
1386
|
+
(nextPanel: DocumentUtilityPanel) => {
|
|
1387
|
+
setUtilityPanel(nextPanel);
|
|
1388
|
+
if (nextPanel !== "comments") {
|
|
1389
|
+
setPendingComment(null);
|
|
1390
|
+
clearCommentFocus();
|
|
1391
|
+
}
|
|
1392
|
+
},
|
|
1393
|
+
[clearCommentFocus],
|
|
1394
|
+
);
|
|
1395
|
+
|
|
1375
1396
|
useEffect(() => {
|
|
1376
1397
|
setPendingComment(null);
|
|
1377
1398
|
setUtilityPanel(null);
|
|
@@ -1512,9 +1533,11 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1512
1533
|
scrollContainerRef={scrollContainerRef}
|
|
1513
1534
|
activeThreadId={activeThreadId}
|
|
1514
1535
|
selectedThreadId={selectedThreadId}
|
|
1536
|
+
onActivateThread={activateCommentThread}
|
|
1515
1537
|
onSelectedThreadChange={setSelectedThreadId}
|
|
1516
1538
|
onHoveredThreadChange={setHoveredThreadId}
|
|
1517
1539
|
currentUserEmail={session?.email}
|
|
1540
|
+
alignToAnchors={hasUtilityRailSpace}
|
|
1518
1541
|
forceVisible
|
|
1519
1542
|
/>
|
|
1520
1543
|
);
|
|
@@ -1566,7 +1589,7 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1566
1589
|
type="button"
|
|
1567
1590
|
className="ms-auto flex size-8 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
1568
1591
|
aria-label={t("editor.toolbar.closeUtilityPanel")}
|
|
1569
|
-
onClick={() =>
|
|
1592
|
+
onClick={() => handleUtilityPanelChange(null)}
|
|
1570
1593
|
>
|
|
1571
1594
|
<IconX size={16} />
|
|
1572
1595
|
</button>
|
|
@@ -1594,7 +1617,7 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1594
1617
|
<div
|
|
1595
1618
|
className="relative flex min-h-0 min-w-0 flex-1"
|
|
1596
1619
|
data-document-print-root
|
|
1597
|
-
|
|
1620
|
+
onClickCapture={(event) => {
|
|
1598
1621
|
const target = event.target as HTMLElement | null;
|
|
1599
1622
|
if (
|
|
1600
1623
|
target?.closest("[data-comments-sidebar], [data-comment-thread]")
|
|
@@ -1626,7 +1649,7 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1626
1649
|
}
|
|
1627
1650
|
onDelete={handleDeleteDocument}
|
|
1628
1651
|
utilityPanel={utilityPanel}
|
|
1629
|
-
onUtilityPanelChange={
|
|
1652
|
+
onUtilityPanelChange={handleUtilityPanelChange}
|
|
1630
1653
|
showCommentsControl={editorCanEdit && !isLocalFileDocument}
|
|
1631
1654
|
onOpenBreadcrumbItem={handleOpenToolbarBreadcrumb}
|
|
1632
1655
|
/>
|
|
@@ -1817,7 +1840,7 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1817
1840
|
// fields.
|
|
1818
1841
|
const primaryEditor = (
|
|
1819
1842
|
<VisualEditor
|
|
1820
|
-
key={`${documentId}:${
|
|
1843
|
+
key={`${documentId}:${collabEditorEnabled && ydoc ? "live-ready" : canEdit && !isLocalFileDocument ? "live-pending" : `snapshot:${document.updatedAt}`}`}
|
|
1821
1844
|
documentId={documentId}
|
|
1822
1845
|
content={
|
|
1823
1846
|
isLocalFileDocument
|
|
@@ -1831,13 +1854,11 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1831
1854
|
}
|
|
1832
1855
|
onChange={handleContentChange}
|
|
1833
1856
|
onSaveContent={handleContentSaveNow}
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
collabSynced={collabEnabled ? collabSynced : true}
|
|
1840
|
-
awareness={collabEnabled ? awareness : null}
|
|
1857
|
+
ydoc={collabEditorEnabled ? ydoc : null}
|
|
1858
|
+
collabSynced={
|
|
1859
|
+
collabEditorEnabled ? collabSynced : true
|
|
1860
|
+
}
|
|
1861
|
+
awareness={collabEditorEnabled ? awareness : null}
|
|
1841
1862
|
user={currentUser}
|
|
1842
1863
|
editable={editorCanEdit}
|
|
1843
1864
|
localFileMode={isLocalFileDocument}
|
|
@@ -1854,7 +1875,7 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1854
1875
|
pendingHighlight={pendingComment?.range ?? null}
|
|
1855
1876
|
onActivateThread={
|
|
1856
1877
|
editorCanEdit && !isLocalFileDocument
|
|
1857
|
-
?
|
|
1878
|
+
? activateCommentThread
|
|
1858
1879
|
: undefined
|
|
1859
1880
|
}
|
|
1860
1881
|
onJoinTitle={joinFirstBodyBlockToTitle}
|
|
@@ -1883,6 +1904,7 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1883
1904
|
})()}
|
|
1884
1905
|
{!bodyHydrationPending &&
|
|
1885
1906
|
!isLocalFileDocument &&
|
|
1907
|
+
canEdit &&
|
|
1886
1908
|
collabLoading ? (
|
|
1887
1909
|
<div
|
|
1888
1910
|
className="mt-4 inline-flex items-center gap-2 rounded-md border border-border bg-muted/40 px-3 py-2 text-xs text-muted-foreground"
|
|
@@ -1910,20 +1932,21 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1910
1932
|
open={utilityPanel !== null}
|
|
1911
1933
|
onOpenChange={(open) => {
|
|
1912
1934
|
if (!open) {
|
|
1913
|
-
|
|
1914
|
-
setPendingComment(null);
|
|
1935
|
+
handleUtilityPanelChange(null);
|
|
1915
1936
|
}
|
|
1916
1937
|
}}
|
|
1917
1938
|
>
|
|
1918
1939
|
<SheetContent
|
|
1919
1940
|
side="right"
|
|
1920
|
-
className="w-[85vw] max-w-sm p-0"
|
|
1941
|
+
className="flex min-h-0 w-[85vw] max-w-sm flex-col overflow-hidden p-0"
|
|
1921
1942
|
aria-describedby={undefined}
|
|
1922
1943
|
>
|
|
1923
1944
|
<SheetHeader className="sr-only">
|
|
1924
1945
|
<SheetTitle>{utilityPanelTitle}</SheetTitle>
|
|
1925
1946
|
</SheetHeader>
|
|
1926
|
-
|
|
1947
|
+
<div className="min-h-0 min-w-0 flex-1 overflow-x-hidden overflow-y-auto">
|
|
1948
|
+
{utilityPanelContent}
|
|
1949
|
+
</div>
|
|
1927
1950
|
</SheetContent>
|
|
1928
1951
|
</Sheet>
|
|
1929
1952
|
) : null}
|
|
@@ -136,8 +136,14 @@ export function ContentFilesSidebarView({
|
|
|
136
136
|
| "scroll"
|
|
137
137
|
>;
|
|
138
138
|
}) {
|
|
139
|
+
const usableData =
|
|
140
|
+
data?.database &&
|
|
141
|
+
Array.isArray(data.items) &&
|
|
142
|
+
Array.isArray(data.properties)
|
|
143
|
+
? data
|
|
144
|
+
: undefined;
|
|
139
145
|
const viewConfig = applyPersonalSidebarViewOverrides(
|
|
140
|
-
|
|
146
|
+
usableData?.database.viewConfig ?? defaultDatabaseViewConfig(),
|
|
141
147
|
overrides,
|
|
142
148
|
);
|
|
143
149
|
const [selectedViewId, setSelectedViewId] = useState(
|
|
@@ -154,10 +160,10 @@ export function ContentFilesSidebarView({
|
|
|
154
160
|
useEffect(() => {
|
|
155
161
|
setConstraintsCleared(false);
|
|
156
162
|
}, [activeFilterKey, activeView.id]);
|
|
157
|
-
const items =
|
|
163
|
+
const items = usableData
|
|
158
164
|
? applyDatabaseView(
|
|
159
|
-
|
|
160
|
-
|
|
165
|
+
usableData.items,
|
|
166
|
+
usableData.properties,
|
|
161
167
|
"",
|
|
162
168
|
constraintsCleared ? [] : activeView.filters,
|
|
163
169
|
activeView.sorts,
|
|
@@ -167,16 +173,18 @@ export function ContentFilesSidebarView({
|
|
|
167
173
|
const groups = databaseVisibleGroups(
|
|
168
174
|
databaseViewItemGroups(
|
|
169
175
|
items,
|
|
170
|
-
|
|
176
|
+
usableData?.properties ?? [],
|
|
171
177
|
activeView.groupByPropertyId,
|
|
172
178
|
),
|
|
173
179
|
activeView.hideEmptyGroups === true,
|
|
174
180
|
);
|
|
175
|
-
const hasFilesHierarchy =
|
|
181
|
+
const hasFilesHierarchy = usableData?.properties.some(
|
|
176
182
|
(property) => property.definition.systemRole === "files_parent",
|
|
177
183
|
);
|
|
178
184
|
const hierarchyItems = hasFilesHierarchy ? items : undefined;
|
|
179
|
-
const hierarchyUniverseItems = hasFilesHierarchy
|
|
185
|
+
const hierarchyUniverseItems = hasFilesHierarchy
|
|
186
|
+
? usableData?.items
|
|
187
|
+
: undefined;
|
|
180
188
|
return (
|
|
181
189
|
<div className="min-w-0">
|
|
182
190
|
{viewConfig.views.length > 1 && (
|
|
@@ -204,7 +212,10 @@ export function ContentFilesSidebarView({
|
|
|
204
212
|
{...labels}
|
|
205
213
|
groups={groups}
|
|
206
214
|
grouped={
|
|
207
|
-
!!databaseViewGroupingProperty(
|
|
215
|
+
!!databaseViewGroupingProperty(
|
|
216
|
+
activeView,
|
|
217
|
+
usableData?.properties ?? [],
|
|
218
|
+
)
|
|
208
219
|
}
|
|
209
220
|
isLoading={isLoading}
|
|
210
221
|
hasActiveConstraints={
|
|
@@ -7,9 +7,10 @@ import {
|
|
|
7
7
|
useActionMutation,
|
|
8
8
|
useActionQuery,
|
|
9
9
|
} from "@agent-native/core/client/hooks";
|
|
10
|
-
import { useT } from "@agent-native/core/client/i18n";
|
|
10
|
+
import { LanguagePicker, useT } from "@agent-native/core/client/i18n";
|
|
11
11
|
import { OrgSwitcher } from "@agent-native/core/client/org";
|
|
12
12
|
import { FeedbackButton } from "@agent-native/core/client/ui";
|
|
13
|
+
import { SidebarFooterActions } from "@agent-native/toolkit/app-shell";
|
|
13
14
|
import {
|
|
14
15
|
closestCenter,
|
|
15
16
|
DndContext,
|
|
@@ -1278,6 +1279,53 @@ export function DocumentSidebar({
|
|
|
1278
1279
|
</Link>
|
|
1279
1280
|
);
|
|
1280
1281
|
|
|
1282
|
+
const collapseButton = (
|
|
1283
|
+
<Tooltip>
|
|
1284
|
+
<TooltipTrigger asChild>
|
|
1285
|
+
<button
|
|
1286
|
+
type="button"
|
|
1287
|
+
aria-label={collapsed ? t("sidebar.expand") : t("sidebar.collapse")}
|
|
1288
|
+
className="flex size-8 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground"
|
|
1289
|
+
onClick={onToggleCollapsed}
|
|
1290
|
+
>
|
|
1291
|
+
{collapsed ? (
|
|
1292
|
+
<IconLayoutSidebarLeftExpand size={16} />
|
|
1293
|
+
) : (
|
|
1294
|
+
<IconLayoutSidebarLeftCollapse size={16} />
|
|
1295
|
+
)}
|
|
1296
|
+
</button>
|
|
1297
|
+
</TooltipTrigger>
|
|
1298
|
+
<TooltipContent side="right">
|
|
1299
|
+
{collapsed ? t("sidebar.expand") : t("sidebar.collapse")}
|
|
1300
|
+
</TooltipContent>
|
|
1301
|
+
</Tooltip>
|
|
1302
|
+
);
|
|
1303
|
+
const searchButton = (
|
|
1304
|
+
<Tooltip>
|
|
1305
|
+
<TooltipTrigger asChild>
|
|
1306
|
+
<button
|
|
1307
|
+
type="button"
|
|
1308
|
+
aria-label={t("sidebar.search")}
|
|
1309
|
+
className="flex size-8 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground"
|
|
1310
|
+
onClick={() => setIsSearching((value) => !value)}
|
|
1311
|
+
>
|
|
1312
|
+
<IconSearch size={16} />
|
|
1313
|
+
</button>
|
|
1314
|
+
</TooltipTrigger>
|
|
1315
|
+
<TooltipContent side="right">{t("sidebar.search")}</TooltipContent>
|
|
1316
|
+
</Tooltip>
|
|
1317
|
+
);
|
|
1318
|
+
const translateButton = (
|
|
1319
|
+
<LanguagePicker variant="ghost-icon" label={t("settings.languageLabel")} />
|
|
1320
|
+
);
|
|
1321
|
+
const feedbackButton = (
|
|
1322
|
+
<FeedbackButton
|
|
1323
|
+
variant={collapsed ? "icon" : "sidebar"}
|
|
1324
|
+
side="right"
|
|
1325
|
+
className={collapsed ? "size-8" : "h-8 min-w-0"}
|
|
1326
|
+
/>
|
|
1327
|
+
);
|
|
1328
|
+
|
|
1281
1329
|
const renderAgentNavButton = () => (
|
|
1282
1330
|
<Link
|
|
1283
1331
|
to="/agent"
|
|
@@ -1832,17 +1880,6 @@ export function DocumentSidebar({
|
|
|
1832
1880
|
if (collapsed) {
|
|
1833
1881
|
return (
|
|
1834
1882
|
<div className="agent-layout-left-drawer flex h-full w-12 flex-col items-center gap-1 border-e border-border bg-sidebar py-3 transition-[width] duration-200 ease-out">
|
|
1835
|
-
<Tooltip>
|
|
1836
|
-
<TooltipTrigger asChild>
|
|
1837
|
-
<button
|
|
1838
|
-
className="w-10 h-10 flex items-center justify-center rounded-lg hover:bg-accent text-muted-foreground hover:text-foreground"
|
|
1839
|
-
onClick={onToggleCollapsed}
|
|
1840
|
-
>
|
|
1841
|
-
<IconLayoutSidebarLeftExpand size={18} />
|
|
1842
|
-
</button>
|
|
1843
|
-
</TooltipTrigger>
|
|
1844
|
-
<TooltipContent>{t("sidebar.expand")}</TooltipContent>
|
|
1845
|
-
</Tooltip>
|
|
1846
1883
|
{renderCollapsedNewButton()}
|
|
1847
1884
|
<Tooltip>
|
|
1848
1885
|
<TooltipTrigger asChild>
|
|
@@ -1876,6 +1913,13 @@ export function DocumentSidebar({
|
|
|
1876
1913
|
</TooltipTrigger>
|
|
1877
1914
|
<TooltipContent>{t("navigation.settings")}</TooltipContent>
|
|
1878
1915
|
</Tooltip>
|
|
1916
|
+
<SidebarFooterActions
|
|
1917
|
+
collapsed
|
|
1918
|
+
feedback={feedbackButton}
|
|
1919
|
+
translate={translateButton}
|
|
1920
|
+
search={searchButton}
|
|
1921
|
+
collapse={collapseButton}
|
|
1922
|
+
/>
|
|
1879
1923
|
</div>
|
|
1880
1924
|
);
|
|
1881
1925
|
}
|
|
@@ -1908,30 +1952,6 @@ export function DocumentSidebar({
|
|
|
1908
1952
|
Content
|
|
1909
1953
|
</span>
|
|
1910
1954
|
</div>
|
|
1911
|
-
<div className="flex items-center gap-0.5">
|
|
1912
|
-
<Tooltip>
|
|
1913
|
-
<TooltipTrigger asChild>
|
|
1914
|
-
<button
|
|
1915
|
-
className="w-9 h-9 flex items-center justify-center rounded-lg hover:bg-accent text-muted-foreground hover:text-foreground"
|
|
1916
|
-
onClick={() => setIsSearching(!isSearching)}
|
|
1917
|
-
>
|
|
1918
|
-
<IconSearch size={16} />
|
|
1919
|
-
</button>
|
|
1920
|
-
</TooltipTrigger>
|
|
1921
|
-
<TooltipContent>{t("sidebar.search")}</TooltipContent>
|
|
1922
|
-
</Tooltip>
|
|
1923
|
-
<Tooltip>
|
|
1924
|
-
<TooltipTrigger asChild>
|
|
1925
|
-
<button
|
|
1926
|
-
className="w-9 h-9 flex items-center justify-center rounded-lg hover:bg-accent text-muted-foreground hover:text-foreground"
|
|
1927
|
-
onClick={onToggleCollapsed}
|
|
1928
|
-
>
|
|
1929
|
-
<IconLayoutSidebarLeftCollapse size={16} />
|
|
1930
|
-
</button>
|
|
1931
|
-
</TooltipTrigger>
|
|
1932
|
-
<TooltipContent>{t("sidebar.collapse")}</TooltipContent>
|
|
1933
|
-
</Tooltip>
|
|
1934
|
-
</div>
|
|
1935
1955
|
</div>
|
|
1936
1956
|
|
|
1937
1957
|
{/* Search */}
|
|
@@ -2098,13 +2118,17 @@ export function DocumentSidebar({
|
|
|
2098
2118
|
{/* Footer */}
|
|
2099
2119
|
<div className="shrink-0 space-y-2 px-3 py-2">
|
|
2100
2120
|
{isCodeMode ? <DevDatabaseLink /> : null}
|
|
2101
|
-
<div className="flex
|
|
2102
|
-
<
|
|
2103
|
-
<
|
|
2104
|
-
<NotionButton />
|
|
2105
|
-
<ThemeToggle />
|
|
2106
|
-
</div>
|
|
2121
|
+
<div className="flex justify-end gap-0.5">
|
|
2122
|
+
<NotionButton />
|
|
2123
|
+
<ThemeToggle />
|
|
2107
2124
|
</div>
|
|
2125
|
+
<SidebarFooterActions
|
|
2126
|
+
feedback={feedbackButton}
|
|
2127
|
+
translate={translateButton}
|
|
2128
|
+
search={searchButton}
|
|
2129
|
+
collapse={collapseButton}
|
|
2130
|
+
className="px-0 py-0"
|
|
2131
|
+
/>
|
|
2108
2132
|
</div>
|
|
2109
2133
|
|
|
2110
2134
|
{/* Resize handle */}
|
|
@@ -3085,6 +3085,7 @@ const enUS = {
|
|
|
3085
3085
|
askAi: "Ask AI",
|
|
3086
3086
|
resolve: "Resolve",
|
|
3087
3087
|
resolved: "Resolved ({{count}})",
|
|
3088
|
+
unanchored: "Highlight unavailable",
|
|
3088
3089
|
reply: "Reply...",
|
|
3089
3090
|
reopen: "Reopen",
|
|
3090
3091
|
agentRegardingText: 'Regarding this text: "{{text}}"',
|
|
@@ -8761,6 +8762,19 @@ const contentReferenceMessagesByLocale = {
|
|
|
8761
8762
|
},
|
|
8762
8763
|
} satisfies Partial<Record<LocaleCode, typeof enUS.editor.reference>>;
|
|
8763
8764
|
|
|
8765
|
+
const commentMessagesByLocale = {
|
|
8766
|
+
"zh-CN": { unanchored: "高亮不可用" },
|
|
8767
|
+
"zh-TW": { unanchored: "無法使用醒目提示" },
|
|
8768
|
+
"es-ES": { unanchored: "Resaltado no disponible" },
|
|
8769
|
+
"fr-FR": { unanchored: "Surlignage indisponible" },
|
|
8770
|
+
"de-DE": { unanchored: "Hervorhebung nicht verfügbar" },
|
|
8771
|
+
"ja-JP": { unanchored: "ハイライトを利用できません" },
|
|
8772
|
+
"ko-KR": { unanchored: "강조 표시를 사용할 수 없음" },
|
|
8773
|
+
"pt-BR": { unanchored: "Destaque indisponível" },
|
|
8774
|
+
"hi-IN": { unanchored: "हाइलाइट उपलब्ध नहीं है" },
|
|
8775
|
+
"ar-SA": { unanchored: "التمييز غير متاح" },
|
|
8776
|
+
} satisfies Partial<Record<LocaleCode, Partial<typeof enUS.comments>>>;
|
|
8777
|
+
|
|
8764
8778
|
function mergeMessages(overrides: PartialMessages): Messages {
|
|
8765
8779
|
return {
|
|
8766
8780
|
root: { ...enUS.root, ...overrides.root },
|
|
@@ -8862,6 +8876,7 @@ function mergeMessagesForLocale(
|
|
|
8862
8876
|
});
|
|
8863
8877
|
return {
|
|
8864
8878
|
...base,
|
|
8879
|
+
comments: { ...base.comments, ...commentMessagesByLocale[locale] },
|
|
8865
8880
|
root: { ...base.root, ...rawLiteralOverrides.root },
|
|
8866
8881
|
team: { ...base.team, ...rawLiteralOverrides.team },
|
|
8867
8882
|
settings: { ...base.settings, ...rawLiteralOverrides.settings },
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
applyText,
|
|
6
6
|
seedFromText,
|
|
7
7
|
} from "@agent-native/core/collab";
|
|
8
|
-
import { isPostgres } from "@agent-native/core/db";
|
|
8
|
+
import { getDbExec, isPostgres } from "@agent-native/core/db";
|
|
9
9
|
import { accessFilter, assertAccess } from "@agent-native/core/sharing";
|
|
10
10
|
import { and, eq, isNull, sql } from "drizzle-orm";
|
|
11
11
|
import { z } from "zod";
|
|
@@ -16,6 +16,30 @@ import { assertDesignHtmlEditIntegrity } from "../shared/html-integrity.js";
|
|
|
16
16
|
import { assertLockedLayersPreserved } from "../shared/locked-layers.js";
|
|
17
17
|
import { sourceContentHash } from "../shared/source-workspace.js";
|
|
18
18
|
|
|
19
|
+
// TEMPORARY diagnostic — remove once we've read a few real conflicts in prod.
|
|
20
|
+
// Conflicts are now benign 409s the framework no longer error-logs; this keeps
|
|
21
|
+
// them visible in Netlify, and `content-conflict`'s cacheIsStale field tells us
|
|
22
|
+
// whether they're the multi-instance stale-cache phantom (→ worth the core
|
|
23
|
+
// reload fix) or a genuine concurrent writer (→ 409-rebase is already correct).
|
|
24
|
+
function logSaveConflictDebug(
|
|
25
|
+
event: string,
|
|
26
|
+
detail: Record<string, unknown>,
|
|
27
|
+
): void {
|
|
28
|
+
console.warn(`[update-file:debug] ${event}`, detail);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 404 via statusCode (NOT status — the action route only reads statusCode) so
|
|
32
|
+
// the client save-outbox treats a gone file as terminal and drops it, instead
|
|
33
|
+
// of looping a masked 500. Used at every missing-file guard, including the
|
|
34
|
+
// post-lock rereads a concurrent delete can hit.
|
|
35
|
+
function fileNotFound(id: string): Error & { statusCode?: number } {
|
|
36
|
+
const err = new Error(`File not found: ${id}`) as Error & {
|
|
37
|
+
statusCode?: number;
|
|
38
|
+
};
|
|
39
|
+
err.statusCode = 404;
|
|
40
|
+
return err;
|
|
41
|
+
}
|
|
42
|
+
|
|
19
43
|
function rowsAffected(result: unknown): number | undefined {
|
|
20
44
|
const candidate = result as {
|
|
21
45
|
rowsAffected?: unknown;
|
|
@@ -169,16 +193,8 @@ export default defineAction({
|
|
|
169
193
|
.limit(1);
|
|
170
194
|
|
|
171
195
|
if (!file) {
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
// entry instead of retrying forever (which turns one orphaned/deleted
|
|
175
|
-
// screen into an update-file 500 storm — see design-save-outbox
|
|
176
|
-
// isTerminalSaveError).
|
|
177
|
-
const notFound = new Error(`File not found: ${id}`) as Error & {
|
|
178
|
-
status?: number;
|
|
179
|
-
};
|
|
180
|
-
notFound.status = 404;
|
|
181
|
-
throw notFound;
|
|
196
|
+
// The row is gone or out of access scope — retry can't succeed.
|
|
197
|
+
throw fileNotFound(id);
|
|
182
198
|
}
|
|
183
199
|
|
|
184
200
|
await assertAccess("design", file.designId, "editor");
|
|
@@ -232,7 +248,9 @@ export default defineAction({
|
|
|
232
248
|
.where(eq(schema.designFiles.id, id))
|
|
233
249
|
.limit(1);
|
|
234
250
|
if (!persistedFile) {
|
|
235
|
-
|
|
251
|
+
// Delete-race: the row passed the access check but is gone now.
|
|
252
|
+
// Same 404 as the outer guard, not a bare 500 the outbox retries.
|
|
253
|
+
throw fileNotFound(id);
|
|
236
254
|
}
|
|
237
255
|
|
|
238
256
|
const persistedContentHash = sourceContentHash(persistedFile.content);
|
|
@@ -390,9 +408,47 @@ export default defineAction({
|
|
|
390
408
|
skippedStaleMirror = true;
|
|
391
409
|
}
|
|
392
410
|
} else {
|
|
393
|
-
|
|
411
|
+
const liveContentHash = sourceContentHash(liveContent);
|
|
412
|
+
// Diagnostic probe (never affects the outcome): `getText` read this
|
|
413
|
+
// instance's Y.Doc cache, never re-hydrated from the DB. Compare it
|
|
414
|
+
// to the freshly persisted text_snapshot so the log distinguishes a
|
|
415
|
+
// multi-instance stale cache (cacheIsStale) from a genuine writer.
|
|
416
|
+
let freshDbLiveHash: string | null = null;
|
|
417
|
+
try {
|
|
418
|
+
const { rows } = await getDbExec().execute({
|
|
419
|
+
sql: "SELECT text_snapshot FROM _collab_docs WHERE doc_id = ?",
|
|
420
|
+
args: [id],
|
|
421
|
+
});
|
|
422
|
+
if (rows.length > 0) {
|
|
423
|
+
freshDbLiveHash = sourceContentHash(
|
|
424
|
+
String(rows[0]?.text_snapshot ?? ""),
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
} catch {
|
|
428
|
+
// diagnostic only
|
|
429
|
+
}
|
|
430
|
+
logSaveConflictDebug("content-conflict", {
|
|
431
|
+
id,
|
|
432
|
+
caller: context?.caller,
|
|
433
|
+
syncCollab,
|
|
434
|
+
operationRevision: operationRevision ?? null,
|
|
435
|
+
expectedVersionHash,
|
|
436
|
+
sentContentHash: sourceContentHash(content),
|
|
437
|
+
liveContentHash,
|
|
438
|
+
persistedMirrorHash: persistedContentHash,
|
|
439
|
+
cacheIsStale:
|
|
440
|
+
freshDbLiveHash !== null &&
|
|
441
|
+
freshDbLiveHash !== liveContentHash,
|
|
442
|
+
freshDbMatchesExpected: freshDbLiveHash === expectedVersionHash,
|
|
443
|
+
});
|
|
444
|
+
// 409 (statusCode), not a bare 500: an expected optimistic-
|
|
445
|
+
// concurrency outcome the framework returns verbatim and the client
|
|
446
|
+
// rebases from, instead of a Sentry-captured fault + retry storm.
|
|
447
|
+
const conflict = new Error(
|
|
394
448
|
"File changed since it was read. Re-read the file and retry.",
|
|
395
|
-
);
|
|
449
|
+
) as Error & { statusCode?: number };
|
|
450
|
+
conflict.statusCode = 409;
|
|
451
|
+
throw conflict;
|
|
396
452
|
}
|
|
397
453
|
}
|
|
398
454
|
}
|
|
@@ -461,7 +517,9 @@ export default defineAction({
|
|
|
461
517
|
// guarded UPDATE alone can still race. Serialize design-file renames in
|
|
462
518
|
// this rare path without using SQLite's fragile async savepoint wrapper.
|
|
463
519
|
await (
|
|
464
|
-
tx as unknown as {
|
|
520
|
+
tx as unknown as {
|
|
521
|
+
execute: (query: unknown) => Promise<unknown>;
|
|
522
|
+
}
|
|
465
523
|
).execute(sql`LOCK TABLE design_files IN SHARE ROW EXCLUSIVE MODE`);
|
|
466
524
|
const [collision] = await tx
|
|
467
525
|
.select({ id: schema.designFiles.id })
|
|
@@ -545,7 +603,7 @@ export default defineAction({
|
|
|
545
603
|
.from(schema.designFiles)
|
|
546
604
|
.where(eq(schema.designFiles.id, id))
|
|
547
605
|
.limit(1);
|
|
548
|
-
if (!confirmed) throw
|
|
606
|
+
if (!confirmed) throw fileNotFound(id);
|
|
549
607
|
const confirmedHash = sourceContentHash(confirmed.content);
|
|
550
608
|
const exactOperationPersisted =
|
|
551
609
|
confirmed.contentOperationSource === operationSource &&
|
|
@@ -622,9 +680,18 @@ export default defineAction({
|
|
|
622
680
|
}
|
|
623
681
|
return;
|
|
624
682
|
}
|
|
625
|
-
|
|
683
|
+
logSaveConflictDebug("retry-exhausted", {
|
|
684
|
+
id,
|
|
685
|
+
caller: context?.caller,
|
|
686
|
+
operationSource: operationSource ?? null,
|
|
687
|
+
operationRevision: operationRevision ?? null,
|
|
688
|
+
expectedVersionHash,
|
|
689
|
+
});
|
|
690
|
+
const exhausted = new Error(
|
|
626
691
|
"File changed repeatedly while it was being saved. Re-read the file and retry.",
|
|
627
|
-
);
|
|
692
|
+
) as Error & { statusCode?: number };
|
|
693
|
+
exhausted.statusCode = 409;
|
|
694
|
+
throw exhausted;
|
|
628
695
|
});
|
|
629
696
|
|
|
630
697
|
// Update the parent design's updatedAt timestamp. This still runs even
|