@jini-ai/ui 0.5.5 → 0.5.6
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/CHANGELOG.md +4 -0
- package/dist/core/theme/validation.js +40 -38
- package/dist/features/a2ui/renderer.js +15 -13
- package/dist/features/about/react/components/AboutTab.js +64 -34
- package/dist/features/about/rules.d.ts +2 -2
- package/dist/features/about/rules.js +156 -129
- package/dist/features/admin-widgets/components/ImagePreviewModal.d.ts +4 -3
- package/dist/features/admin-widgets/components/ImagePreviewModal.js +6 -10
- package/dist/features/admin-widgets/components/InfoTip.d.ts +3 -2
- package/dist/features/admin-widgets/components/InfoTip.hooks.js +2 -0
- package/dist/features/admin-widgets/components/InfoTip.js +34 -7
- package/dist/features/admin-widgets/components/InfoTip.rules.d.ts +13 -0
- package/dist/features/admin-widgets/components/InfoTip.rules.js +43 -10
- package/dist/features/admin-widgets/components/Lightbox/Lightbox.d.ts +30 -0
- package/dist/features/admin-widgets/components/Lightbox/Lightbox.hooks.d.ts +40 -0
- package/dist/features/admin-widgets/components/Lightbox/Lightbox.hooks.js +128 -0
- package/dist/features/admin-widgets/components/Lightbox/Lightbox.js +18 -0
- package/dist/features/admin-widgets/components/MiddleTruncate.d.ts +14 -0
- package/dist/features/admin-widgets/components/MiddleTruncate.js +12 -0
- package/dist/features/admin-widgets/components/MiddleTruncate.rules.d.ts +20 -0
- package/dist/features/admin-widgets/components/MiddleTruncate.rules.js +21 -0
- package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.d.ts +18 -0
- package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.hooks.d.ts +16 -0
- package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.hooks.js +45 -0
- package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.js +18 -0
- package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.rules.d.ts +32 -0
- package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.rules.js +26 -0
- package/dist/features/admin-widgets/components/SuggestInput/SuggestInput.hooks.js +29 -14
- package/dist/features/admin-widgets/index.d.ts +14 -2
- package/dist/features/admin-widgets/index.js +7 -1
- package/dist/features/admin-widgets/styles/admin-widgets.css +1 -0
- package/dist/features/asset-grid/react/components/AssetCard.js +7 -1
- package/dist/features/asset-grid/react/components/AssetGrid.d.ts +1 -1
- package/dist/features/asset-grid/react/components/AssetGrid.js +38 -18
- package/dist/features/asset-grid/react/components/useAssetGridFilters.d.ts +21 -0
- package/dist/features/asset-grid/react/components/useAssetGridFilters.js +19 -0
- package/dist/features/asset-grid/react/hooks/useAssetGridKeyboardShortcuts.js +16 -11
- package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.d.ts +2 -0
- package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.js +35 -11
- package/dist/features/asset-tree-browser/react/components/AssetTreeBrowser.d.ts +6 -6
- package/dist/features/asset-tree-browser/react/components/AssetTreeBrowser.js +10 -129
- package/dist/features/asset-tree-browser/react/components/AssetTreeBrowserParts.d.ts +50 -0
- package/dist/features/asset-tree-browser/react/components/AssetTreeBrowserParts.js +95 -0
- package/dist/features/asset-tree-browser/react/components/useAssetTreeBrowserController.d.ts +56 -0
- package/dist/features/asset-tree-browser/react/components/useAssetTreeBrowserController.js +80 -0
- package/dist/features/asset-tree-browser/rules.js +40 -22
- package/dist/features/browser-chrome/rules.js +74 -70
- package/dist/features/charts/browser.js +11 -5
- package/dist/features/charts/react.js +2 -2
- package/dist/features/charts/table.js +2 -2
- package/dist/features/command-palette/react/hooks/useCommandPalette.js +3 -9
- package/dist/features/connectors/ConnectorsBrowser.d.ts +4 -4
- package/dist/features/connectors/ConnectorsBrowser.js +26 -14
- package/dist/features/connectors/components/ConnectorCard.js +67 -38
- package/dist/features/connectors/components/ConnectorDetailDrawer.js +62 -31
- package/dist/features/connectors/components/ConnectorGrid.js +11 -6
- package/dist/features/connectors/hooks/useConnectorAuthorization.js +6 -0
- package/dist/features/connectors/hooks/useConnectorCatalog.d.ts +2 -0
- package/dist/features/connectors/hooks/useConnectorCatalog.js +32 -13
- package/dist/features/connectors/hooks/useWiredConnectorsBrowser.d.ts +3 -3
- package/dist/features/connectors/rules.d.ts +8 -1
- package/dist/features/connectors/rules.js +42 -20
- package/dist/features/connectors/types.d.ts +11 -0
- package/dist/features/execution/constants.d.ts +4 -10
- package/dist/features/execution/constants.js +9 -94
- package/dist/features/execution/react/components/ByokProviderForm.d.ts +2 -4
- package/dist/features/execution/react/components/ByokProviderForm.js +140 -43
- package/dist/features/execution/react/components/ExecutionTab.d.ts +1 -2
- package/dist/features/execution/react/components/ExecutionTab.js +52 -30
- package/dist/features/execution/react/components/LocalCliAgentCard.js +12 -3
- package/dist/features/execution/react/components/LocalCliAgentList.js +14 -3
- package/dist/features/execution/react/components/LocalCliAgentSettings.js +14 -3
- package/dist/features/execution/react/hooks/useLocalCliAgentCard.js +54 -36
- package/dist/features/execution/react/hooks/useReasoningControl.js +114 -78
- package/dist/features/execution/rules.d.ts +2 -2
- package/dist/features/execution/rules.js +31 -15
- package/dist/features/execution/types.d.ts +4 -42
- package/dist/features/execution/types.js +0 -17
- package/dist/features/external-mcp/react/components/ExternalMcpTab.d.ts +6 -2
- package/dist/features/external-mcp/react/components/ExternalMcpTab.js +9 -2
- package/dist/features/file-dropzone/react/components/FileDropzone.js +24 -25
- package/dist/features/file-dropzone/react/components/FileDropzoneLightbox.d.ts +2 -2
- package/dist/features/file-dropzone/react/components/FileDropzoneLightbox.js +35 -30
- package/dist/features/file-dropzone/react/components/FileDropzoneThumbnailGrid.js +30 -24
- package/dist/features/file-dropzone/react/components/FileDropzoneZone.d.ts +14 -0
- package/dist/features/file-dropzone/react/components/FileDropzoneZone.js +26 -0
- package/dist/features/html-editor/canvas-content-wrapper.js +13 -13
- package/dist/features/html-editor/canvas-embed-placeholders.js +46 -37
- package/dist/features/html-editor/css.js +76 -69
- package/dist/features/html-editor/editor-blocks.js +47 -33
- package/dist/features/html-editor/editor-serialization.js +6 -5
- package/dist/features/html-editor/protected-elements.js +39 -28
- package/dist/features/html-editor/react/hooks/useInteractiveHtmlEditor.js +4 -4
- package/dist/features/html-editor/source-splice.js +51 -30
- package/dist/features/i18n/dictionaries/settings-dialog.ja.js +1 -0
- package/dist/features/i18n/dictionaries/settings-dialog.zh-CN.js +1 -0
- package/dist/features/i18n/dictionaries/settings-dialog.zh-TW.js +1 -0
- package/dist/features/i18n/locale.js +20 -16
- package/dist/features/iframe-pool/react/dom-sync.js +8 -6
- package/dist/features/integrations/react/components/CodexInstallToggleButton.js +19 -4
- package/dist/features/integrations/react/components/IntegrationsTab.js +60 -14
- package/dist/features/interactive-ui/interactive-ui.css +1 -1
- package/dist/features/interactive-ui/providers/shadcn/data-table.js +10 -7
- package/dist/features/lexical-rich-text-editor/mention-parser.js +60 -57
- package/dist/features/lexical-rich-text-editor/react/hooks/useKeyboardCommands.js +33 -38
- package/dist/features/lexical-rich-text-editor/rules.js +87 -74
- package/dist/features/lexical-rich-text-editor/serialize.js +27 -25
- package/dist/features/mcp-ui/secret-card.js +111 -61
- package/dist/features/mcp-ui/surfaces/confirmation.js +39 -21
- package/dist/features/mcp-ui/surfaces/document.d.ts +1 -1
- package/dist/features/mcp-ui/surfaces/document.js +10 -2
- package/dist/features/mcp-ui/surfaces/fields.js +30 -50
- package/dist/features/mcp-ui/surfaces/form.d.ts +6 -0
- package/dist/features/mcp-ui/surfaces/form.js +43 -21
- package/dist/features/mcp-ui/surfaces/outcome.js +30 -20
- package/dist/features/mcp-ui/surfaces/text-input.js +33 -21
- package/dist/features/mcp-ui/surfaces/tokens.d.ts +6 -1
- package/dist/features/mcp-ui/surfaces/tokens.js +5 -2
- package/dist/features/media-providers/constants.d.ts +1 -0
- package/dist/features/media-providers/constants.js +2 -81
- package/dist/features/media-providers/index.d.ts +1 -1
- package/dist/features/media-providers/ports.d.ts +2 -0
- package/dist/features/media-providers/react/components/MediaProviderCard.d.ts +22 -0
- package/dist/features/media-providers/react/components/MediaProviderCard.js +50 -0
- package/dist/features/media-providers/react/components/MediaProvidersTab.d.ts +5 -6
- package/dist/features/media-providers/react/components/MediaProvidersTab.js +40 -72
- package/dist/features/media-providers/react/components/media-providers-tab-labels.d.ts +11 -0
- package/dist/features/media-providers/react/components/media-providers-tab-labels.js +57 -0
- package/dist/features/media-providers/react/hooks/useMediaProvidersTab.d.ts +4 -2
- package/dist/features/media-providers/react/hooks/useMediaProvidersTab.js +37 -23
- package/dist/features/media-providers/rules.js +17 -10
- package/dist/features/media-providers/types.d.ts +4 -22
- package/dist/features/media-providers/types.js +3 -8
- package/dist/features/memory/formatters.js +127 -92
- package/dist/features/memory/react/components/MemoryConnectedPanel.js +92 -36
- package/dist/features/memory/react/components/MemoryList.js +37 -18
- package/dist/features/memory/react/hooks/useMemoryExtractions.store.js +42 -36
- package/dist/features/notifications/react/components/NotificationsTab.js +28 -28
- package/dist/features/observability/long-task.d.ts +0 -12
- package/dist/features/observability/long-task.js +15 -19
- package/dist/features/observability/resource-error.d.ts +0 -10
- package/dist/features/observability/resource-error.js +18 -13
- package/dist/features/observability/stuck-run.js +10 -6
- package/dist/features/observability/telemetry-url.d.ts +6 -0
- package/dist/features/observability/telemetry-url.js +16 -0
- package/dist/features/observability/white-screen.js +12 -24
- package/dist/features/panel-kit/fetch-query/adapter.react.d.ts +2 -2
- package/dist/features/panel-kit/fetch-query/adapter.react.js +4 -2
- package/dist/features/panel-kit/fetch-query/adapter.tanstack.js +46 -33
- package/dist/features/panel-kit/helpers/standing-draft-local-backup.js +3 -1
- package/dist/features/panel-kit/hooks/use-settings-slice.hooks.js +3 -1
- package/dist/features/privacy/react/components/PrivacyTab.js +24 -13
- package/dist/features/progress-card/components/ProgressCard.js +7 -3
- package/dist/features/progress-card/reference-adapters.js +59 -54
- package/dist/features/project-locations/react/components/ProjectLocationsTab.js +24 -15
- package/dist/features/project-locations/react/hooks/useProjectLocationsTab.d.ts +8 -0
- package/dist/features/project-locations/react/hooks/useProjectLocationsTab.js +31 -23
- package/dist/features/resource-dashboard/react/components/ResourceBoard.js +6 -47
- package/dist/features/resource-dashboard/react/components/ResourceBoardView.d.ts +2 -2
- package/dist/features/resource-dashboard/react/components/ResourceBoardView.js +3 -6
- package/dist/features/resource-dashboard/react/components/ResourceBoardViewParts.d.ts +15 -0
- package/dist/features/resource-dashboard/react/components/ResourceBoardViewParts.js +35 -0
- package/dist/features/resource-dashboard/react/components/ResourceCard.js +5 -16
- package/dist/features/resource-dashboard/react/components/ResourceCardParts.d.ts +34 -0
- package/dist/features/resource-dashboard/react/components/ResourceCardParts.js +34 -0
- package/dist/features/resource-dashboard/react/components/ResourceRowList.js +3 -1
- package/dist/features/resource-dashboard/react/components/ResourceRowListItem.js +2 -2
- package/dist/features/resource-dashboard/react/components/ResourceRowListItemParts.d.ts +19 -0
- package/dist/features/resource-dashboard/react/components/ResourceRowListItemParts.js +11 -0
- package/dist/features/resource-dashboard/react/components/ResourceRowListView.d.ts +1 -1
- package/dist/features/resource-dashboard/react/components/ResourceRowListView.js +4 -4
- package/dist/features/resource-dashboard/react/components/ResourceRowListViewParts.d.ts +10 -0
- package/dist/features/resource-dashboard/react/components/ResourceRowListViewParts.js +20 -0
- package/dist/features/resource-dashboard/react/components/resourceBoardProps.d.ts +48 -0
- package/dist/features/resource-dashboard/react/components/resourceBoardProps.js +70 -0
- package/dist/features/resource-dashboard/react/components/resourceRowListProps.d.ts +19 -0
- package/dist/features/resource-dashboard/react/components/resourceRowListProps.js +14 -0
- package/dist/features/resource-dashboard/react/components/translatedErrorLabelProps.d.ts +16 -0
- package/dist/features/resource-dashboard/react/components/translatedErrorLabelProps.js +13 -0
- package/dist/features/resource-dashboard/rules.js +2 -8
- package/dist/features/sketch-editor/dom.js +150 -99
- package/dist/features/sketch-editor/react/components/SketchEditor.js +26 -15
- package/dist/features/skills/react/components/SkillDraftForm.js +13 -19
- package/dist/features/skills/react/components/SkillDraftForm.labels.d.ts +31 -0
- package/dist/features/skills/react/components/SkillDraftForm.labels.js +54 -0
- package/dist/features/skills/react/components/SkillRow.js +38 -18
- package/dist/features/skills/react/components/SkillRow.labels.d.ts +7 -0
- package/dist/features/skills/react/components/SkillRow.labels.js +18 -0
- package/dist/features/skills/react/components/SkillsTab.js +24 -14
- package/dist/features/skills/react/components/SkillsTab.labels.d.ts +7 -0
- package/dist/features/skills/react/components/SkillsTab.labels.js +11 -0
- package/dist/features/skills/rules.js +32 -23
- package/dist/features/source-config-list/agent-handles.js +2 -1
- package/dist/features/source-config-list/dependencies.js +53 -49
- package/dist/features/source-config-list/index.d.ts +3 -3
- package/dist/features/source-config-list/react/components/SourceConfigAddForm.d.ts +6 -5
- package/dist/features/source-config-list/react/components/SourceConfigAddForm.js +38 -7
- package/dist/features/source-config-list/react/components/SourceConfigField.js +69 -25
- package/dist/features/source-config-list/react/components/SourceConfigItemCard.d.ts +6 -5
- package/dist/features/source-config-list/react/components/SourceConfigItemCard.js +76 -20
- package/dist/features/source-config-list/react/components/SourceConfigListView.d.ts +5 -3
- package/dist/features/source-config-list/react/components/SourceConfigListView.js +33 -3
- package/dist/features/source-config-list/react/components/SourceConfigTestControl.js +17 -1
- package/dist/features/source-config-list/react/components/SourceConfigTrustOptions.d.ts +11 -0
- package/dist/features/source-config-list/react/components/SourceConfigTrustOptions.js +12 -0
- package/dist/features/source-config-list/types.d.ts +14 -0
- package/dist/features/tab-launcher-menu/react/components/TabLauncherMenu.js +25 -1
- package/dist/features/tab-launcher-menu/react/hooks/useTabLauncherMenu.js +15 -16
- package/dist/features/tab-strip/react/components/TabBar.js +8 -1
- package/dist/features/tabbed-dialog/react/components/TabbedDialog.js +8 -30
- package/dist/features/tabbed-dialog/react/components/TabbedDialogChrome.d.ts +15 -0
- package/dist/features/tabbed-dialog/react/components/TabbedDialogChrome.js +8 -0
- package/dist/features/tabbed-dialog/react/components/TabbedDialogHeader.d.ts +12 -0
- package/dist/features/tabbed-dialog/react/components/TabbedDialogHeader.js +6 -0
- package/dist/features/tabbed-dialog/react/components/TabbedDialogSidebar.d.ts +27 -0
- package/dist/features/tabbed-dialog/react/components/TabbedDialogSidebar.js +12 -0
- package/dist/features/tabbed-dialog/react/components/tabbedDialogView.d.ts +43 -0
- package/dist/features/tabbed-dialog/react/components/tabbedDialogView.js +59 -0
- package/dist/features/version-manager/dependencies.d.ts +3 -1
- package/dist/features/version-manager/dependencies.js +3 -2
- package/dist/features/version-manager/react/components/VersionManagerModal.js +10 -6
- package/dist/features/version-manager/react/hooks/useVersionManager.js +7 -4
- package/dist/features/viewer-shell/react/components/CommentSidePanel.js +103 -50
- package/dist/features/viewer-shell/react/components/MarkdownSplitPane.js +28 -8
- package/dist/features/viewer-shell/react/hooks/useCommentReorder.js +22 -16
- package/dist/features/viewer-shell/rules.js +19 -23
- package/dist/react/components/CustomSelect.d.ts +38 -1
- package/dist/react/components/CustomSelect.js +94 -38
- package/dist/react/components/OnboardingDropdown.js +20 -8
- package/dist/react/components/RemixIcon.stylesheet.js +3 -1
- package/dist/react/components/Toast.js +13 -2
- package/dist/react/components/TooltipLayer.js +5 -14
- package/dist/react/components/WorkingDirPicker.js +78 -50
- package/dist/react/components/remixicon-font-recovery.js +38 -30
- package/dist/react/components/select-popover.d.ts +8 -6
- package/dist/react/components/select-popover.js +33 -29
- package/dist/react/mcp-ui/McpUiHost.d.ts +1 -1
- package/dist/react/mcp-ui/McpUiHost.js +10 -11
- package/dist/react/mcp-ui/host-message-source.d.ts +14 -2
- package/dist/react/mcp-ui/host-message-source.js +15 -5
- package/dist/react/mcp-ui/mcp-ui-host-frame.d.ts +41 -0
- package/dist/react/mcp-ui/mcp-ui-host-frame.js +39 -0
- package/dist/renderers/annotation-canvas/react/components/AnnotationCanvas.js +97 -69
- package/dist/renderers/preview-modal-shell/react/components/PreviewModalShell.js +59 -30
- package/dist/renderers/react/components/ArtifactView.js +9 -6
- package/dist/renderers/srcdoc/build.d.ts +4 -1
- package/dist/renderers/srcdoc/build.js +62 -48
- package/dist/styles/admin.css +29 -14
- package/dist/styles/lightbox.css +142 -0
- package/dist/styles/variables.css +128 -0
- package/dist/theme/apply.js +9 -5
- package/dist/utils/auto-open-file.d.ts +2 -1
- package/dist/utils/auto-open-file.js +69 -85
- package/dist/utils/markdown-scroll-sync.js +108 -87
- package/dist/utils/polygon-selection.js +25 -26
- package/dist/utils/smooth-scroll-to-top.d.ts +1 -1
- package/dist/utils/smooth-scroll-to-top.js +2 -32
- package/dist/utils/sse.js +26 -26
- package/dist/utils/unit-bezier.d.ts +7 -0
- package/dist/utils/unit-bezier.js +38 -0
- package/package.json +7 -10
- package/dist/features/admin-widgets/components/ImagePreviewModal.hooks.d.ts +0 -13
- package/dist/features/admin-widgets/components/ImagePreviewModal.hooks.js +0 -46
|
@@ -27,27 +27,22 @@ import { testStatusLabel } from '../../rules.js';
|
|
|
27
27
|
*/
|
|
28
28
|
export function NotificationsTab({ preferences, onChange, testNotificationTitle, testNotificationBody, labels, agentHandle, }) {
|
|
29
29
|
const t = useT();
|
|
30
|
-
const successSoundHandles = agentHandle
|
|
31
|
-
? buildAgentListHandles({ prefix: agentSubHandle({ base: agentHandle, action: 'success-sound' }), ids: SUCCESS_SOUNDS.map((s) => s.id) })
|
|
32
|
-
: undefined;
|
|
33
|
-
const failureSoundHandles = agentHandle
|
|
34
|
-
? buildAgentListHandles({ prefix: agentSubHandle({ base: agentHandle, action: 'failure-sound' }), ids: FAILURE_SOUNDS.map((s) => s.id) })
|
|
35
|
-
: undefined;
|
|
36
30
|
const [permission, setPermission] = useState(() => notificationPermission());
|
|
37
31
|
const [testStatus, setTestStatus] = useState(null);
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
const
|
|
32
|
+
const label = ({ key, fallback }, _options = {}) => labels?.[key] ?? t(fallback);
|
|
33
|
+
const completionSoundTitle = label({ key: 'completionSoundTitle', fallback: 'Completion sound' });
|
|
34
|
+
const completionSoundHint = label({ key: 'completionSoundHint', fallback: 'Play a sound when a task finishes.' });
|
|
35
|
+
const successSoundLabel = label({ key: 'successSoundLabel', fallback: 'Success sound' });
|
|
36
|
+
const failureSoundLabel = label({ key: 'failureSoundLabel', fallback: 'Failure sound' });
|
|
37
|
+
const desktopTitle = label({ key: 'desktopTitle', fallback: 'Desktop notifications' });
|
|
38
|
+
const desktopHint = label({ key: 'desktopHint', fallback: 'Show a system notification when a task finishes.' });
|
|
39
|
+
const desktopUnsupported = label({ key: 'desktopUnsupported', fallback: 'Desktop notifications are not supported in this browser.' });
|
|
40
|
+
const desktopBlocked = label({ key: 'desktopBlocked', fallback: 'Desktop notifications are blocked. Enable them in your browser settings.' });
|
|
41
|
+
const sendTestLabel = label({ key: 'sendTestLabel', fallback: 'Send test notification' });
|
|
42
|
+
const testSentLabel = label({ key: 'testSentLabel', fallback: 'Test notification sent.' });
|
|
43
|
+
const testFailedLabel = label({ key: 'testFailedLabel', fallback: 'Could not send a test notification.' });
|
|
44
|
+
const activeLabel = label({ key: 'activeLabel', fallback: 'On' });
|
|
45
|
+
const offLabel = label({ key: 'offLabel', fallback: 'Off' });
|
|
51
46
|
const toggleSound = () => {
|
|
52
47
|
const next = !preferences.soundEnabled;
|
|
53
48
|
onChange({ soundEnabled: next });
|
|
@@ -76,16 +71,21 @@ export function NotificationsTab({ preferences, onChange, testNotificationTitle,
|
|
|
76
71
|
// outcome here rather than getting their own dead states.
|
|
77
72
|
setTestStatus(result === 'shown' ? 'sent' : 'failed');
|
|
78
73
|
};
|
|
79
|
-
return (_jsxs("section", { className: "jini-settings-section jini-settings-notifications", children: [_jsxs("div", { className: "jini-settings-subsection", children: [_jsxs("div", { className: "jini-settings-notify-card", children: [_jsxs("div", { className: "jini-settings-notify-card-header", children: [_jsx("h4", { children: completionSoundTitle }), _jsx(
|
|
80
|
-
onChange({ successSoundId: sound.id });
|
|
81
|
-
playSound(sound.id);
|
|
82
|
-
}, ...agentHandleProps({}, { base: successSoundHandles?.[index], ...({ role: 'button', label: t(sound.labelKey) }) }), children: _jsx("span", { className: "jini-seg-title", children: t(sound.labelKey) }) }, sound.id))) })] }), _jsxs("div", { className: "jini-settings-field", children: [_jsx("label", { children: failureSoundLabel }), _jsx("div", { className: "jini-seg-control", role: "group", "aria-label": failureSoundLabel, style: { '--seg-cols': FAILURE_SOUNDS.length }, children: FAILURE_SOUNDS.map((sound, index) => (_jsx("button", { type: "button", className: 'jini-seg-btn' + (preferences.failureSoundId === sound.id ? ' active' : ''), "aria-pressed": preferences.failureSoundId === sound.id, onClick: () => {
|
|
83
|
-
onChange({ failureSoundId: sound.id });
|
|
84
|
-
playSound(sound.id);
|
|
85
|
-
}, ...agentHandleProps({}, { base: failureSoundHandles?.[index], ...({ role: 'button', label: t(sound.labelKey) }) }), children: _jsx("span", { className: "jini-seg-title", children: t(sound.labelKey) }) }, sound.id))) })] })] })) : null] }), _jsxs("div", { className: "jini-settings-subsection", children: [_jsxs("div", { className: "jini-settings-notify-card", children: [_jsxs("div", { className: "jini-settings-notify-card-header", children: [_jsx("h4", { children: desktopTitle }), _jsx("div", { className: "jini-seg-control", role: "group", "aria-label": desktopTitle, style: { '--seg-cols': 1 }, children: _jsx("button", { type: "button", className: 'jini-seg-btn' + (preferences.desktopEnabled ? ' active' : ''), "aria-pressed": preferences.desktopEnabled, "aria-label": desktopTitle, disabled: permission === 'unsupported', onClick: () => {
|
|
86
|
-
void toggleDesktop();
|
|
87
|
-
}, ...agentHandleProps({}, { base: agentHandle, ...({ action: 'desktop-toggle', role: 'button', label: desktopTitle }) }), children: _jsx("span", { className: "jini-seg-title", children: preferences.desktopEnabled ? activeLabel : offLabel }) }) })] }), _jsx("p", { className: "jini-hint", children: desktopHint })] }), permission === 'unsupported' ? _jsx("p", { className: "jini-hint", children: desktopUnsupported }) : null, permission === 'denied' ? _jsx("p", { className: "jini-hint", children: desktopBlocked }) : null, preferences.desktopEnabled && permission === 'granted' ? (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "jini-button jini-button-ghost", onClick: () => {
|
|
74
|
+
return (_jsxs("section", { className: "jini-settings-section jini-settings-notifications", children: [_jsxs("div", { className: "jini-settings-subsection", children: [_jsxs("div", { className: "jini-settings-notify-card", children: [_jsxs("div", { className: "jini-settings-notify-card-header", children: [_jsx("h4", { children: completionSoundTitle }), _jsx(NotificationToggle, { title: completionSoundTitle, enabled: preferences.soundEnabled, onToggle: toggleSound, activeLabel: activeLabel, offLabel: offLabel, agentHandle: agentHandle, action: "sound-toggle" })] }), _jsx("p", { className: "jini-hint", children: completionSoundHint })] }), preferences.soundEnabled ? (_jsxs(_Fragment, { children: [_jsx(SoundPicker, { label: successSoundLabel, sounds: SUCCESS_SOUNDS, selectedSoundId: preferences.successSoundId, preferenceKey: "successSoundId", onChange: onChange, previewSound: playSound, agentHandle: agentHandle, action: "success-sound" }), _jsx(SoundPicker, { label: failureSoundLabel, sounds: FAILURE_SOUNDS, selectedSoundId: preferences.failureSoundId, preferenceKey: "failureSoundId", onChange: onChange, previewSound: playSound, agentHandle: agentHandle, action: "failure-sound" })] })) : null] }), _jsxs("div", { className: "jini-settings-subsection", children: [_jsxs("div", { className: "jini-settings-notify-card", children: [_jsxs("div", { className: "jini-settings-notify-card-header", children: [_jsx("h4", { children: desktopTitle }), _jsx(NotificationToggle, { title: desktopTitle, enabled: preferences.desktopEnabled, onToggle: toggleDesktop, activeLabel: activeLabel, offLabel: offLabel, agentHandle: agentHandle, action: "desktop-toggle", disabled: permission === 'unsupported' })] }), _jsx("p", { className: "jini-hint", children: desktopHint })] }), permission === 'unsupported' ? _jsx("p", { className: "jini-hint", children: desktopUnsupported }) : null, permission === 'denied' ? _jsx("p", { className: "jini-hint", children: desktopBlocked }) : null, preferences.desktopEnabled && permission === 'granted' ? (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "jini-button jini-button-ghost", onClick: () => {
|
|
88
75
|
void sendTestNotification();
|
|
89
76
|
}, ...agentHandleProps({}, { base: agentHandle, ...({ action: 'send-test', role: 'button', label: sendTestLabel }) }), children: sendTestLabel }), testStatus ? (_jsx("p", { className: "jini-hint", role: "status", children: testStatusLabel(testStatus, { testSentLabel, testFailedLabel }) })) : null] })) : null] })] }));
|
|
90
77
|
}
|
|
78
|
+
function NotificationToggle({ title, enabled, onToggle, activeLabel, offLabel, agentHandle, action, disabled, }) {
|
|
79
|
+
return (_jsx("div", { className: "jini-seg-control", role: "group", "aria-label": title, style: { '--seg-cols': 1 }, children: _jsx("button", { type: "button", className: 'jini-seg-btn' + (enabled ? ' active' : ''), "aria-pressed": enabled, "aria-label": title, disabled: disabled, onClick: () => { void onToggle(); }, ...agentHandleProps({}, { base: agentHandle, action, role: 'button', label: title }), children: _jsx("span", { className: "jini-seg-title", children: enabled ? activeLabel : offLabel }) }) }));
|
|
80
|
+
}
|
|
81
|
+
function SoundPicker({ label, sounds, selectedSoundId, preferenceKey, onChange, previewSound, agentHandle, action, }) {
|
|
82
|
+
const t = useT();
|
|
83
|
+
const handles = agentHandle
|
|
84
|
+
? buildAgentListHandles({ prefix: agentSubHandle({ base: agentHandle, action }), ids: sounds.map((sound) => sound.id) })
|
|
85
|
+
: undefined;
|
|
86
|
+
return (_jsxs("div", { className: "jini-settings-field", children: [_jsx("label", { children: label }), _jsx("div", { className: "jini-seg-control", role: "group", "aria-label": label, style: { '--seg-cols': sounds.length }, children: sounds.map((sound, index) => (_jsx("button", { type: "button", className: 'jini-seg-btn' + (selectedSoundId === sound.id ? ' active' : ''), "aria-pressed": selectedSoundId === sound.id, onClick: () => {
|
|
87
|
+
onChange({ [preferenceKey]: sound.id });
|
|
88
|
+
previewSound(sound.id);
|
|
89
|
+
}, ...agentHandleProps({}, { base: handles?.[index], role: 'button', label: t(sound.labelKey) }), children: _jsx("span", { className: "jini-seg-title", children: t(sound.labelKey) }) }, sound.id))) })] }));
|
|
90
|
+
}
|
|
91
91
|
//# sourceMappingURL=NotificationsTab.js.map
|
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Long task observer.
|
|
3
|
-
*
|
|
4
|
-
* Reports a `client_long_task` event whenever the main thread is blocked
|
|
5
|
-
* for more than `minDurationMs`. Long tasks are a strong proxy for
|
|
6
|
-
* perceived UI lag — a 500 ms task drops 30 consecutive frames at 60 fps,
|
|
7
|
-
* which the user reads as "stuck".
|
|
8
|
-
*
|
|
9
|
-
* Threshold defaults to 100 ms rather than the W3C 50 ms minimum because
|
|
10
|
-
* the browser already filters at 50 ms; bumping to 100 ms here halves the
|
|
11
|
-
* event volume while still catching every task a human notices.
|
|
12
|
-
*/
|
|
13
1
|
import { type SafetyEventReporter } from './ports.js';
|
|
14
2
|
export interface LongTaskObserverOptions {
|
|
15
3
|
reporter?: SafetyEventReporter | undefined;
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* the browser already filters at 50 ms; bumping to 100 ms here halves the
|
|
11
11
|
* event volume while still catching every task a human notices.
|
|
12
12
|
*/
|
|
13
|
+
import { telemetryUrl } from './telemetry-url.js';
|
|
13
14
|
import { noopSafetyEventReporter } from './ports.js';
|
|
14
15
|
const DEFAULT_MIN_DURATION_MS = 100;
|
|
15
16
|
let observer = null;
|
|
@@ -46,37 +47,32 @@ export function installLongTaskObserver(options = {}) {
|
|
|
46
47
|
container_type: attribution?.containerType,
|
|
47
48
|
container_name: attribution?.containerName,
|
|
48
49
|
// containerSrc can carry a query string; trimmed to origin+pathname.
|
|
49
|
-
container_src_origin:
|
|
50
|
+
container_src_origin: telemetryUrl({ value: attribution?.containerSrc }),
|
|
50
51
|
});
|
|
51
52
|
}
|
|
52
53
|
});
|
|
53
|
-
|
|
54
|
-
observer
|
|
55
|
-
|
|
56
|
-
catch {
|
|
57
|
-
// Older engines sometimes throw when `buffered` is requested.
|
|
58
|
-
try {
|
|
59
|
-
observer.observe({ type: 'longtask' });
|
|
60
|
-
}
|
|
61
|
-
catch {
|
|
62
|
-
observer = null;
|
|
63
|
-
return () => undefined;
|
|
64
|
-
}
|
|
54
|
+
if (!observeLongTasks({ observe: (options) => observer.observe(options) })) {
|
|
55
|
+
observer = null;
|
|
56
|
+
return () => undefined;
|
|
65
57
|
}
|
|
66
58
|
return () => {
|
|
67
59
|
observer?.disconnect();
|
|
68
60
|
observer = null;
|
|
69
61
|
};
|
|
70
62
|
}
|
|
71
|
-
function
|
|
72
|
-
if (typeof value !== 'string' || value.length === 0)
|
|
73
|
-
return undefined;
|
|
63
|
+
function observeLongTasks({ observe }, _options = {}) {
|
|
74
64
|
try {
|
|
75
|
-
|
|
76
|
-
return `${parsed.origin}${parsed.pathname}`;
|
|
65
|
+
observe({ type: 'longtask', buffered: true });
|
|
77
66
|
}
|
|
78
67
|
catch {
|
|
79
|
-
|
|
68
|
+
// Older engines sometimes throw when `buffered` is requested.
|
|
69
|
+
try {
|
|
70
|
+
observe({ type: 'longtask' });
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
80
75
|
}
|
|
76
|
+
return true;
|
|
81
77
|
}
|
|
82
78
|
//# sourceMappingURL=long-task.js.map
|
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resource-loading error observer.
|
|
3
|
-
*
|
|
4
|
-
* The bubbling `error` event (registered with capture=false) catches
|
|
5
|
-
* thrown JS errors but NOT failed resource loads — `<script>`, `<link>`,
|
|
6
|
-
* `<img>`, etc. emit an `error` event that does not propagate. To pick
|
|
7
|
-
* those up we register a *capturing* listener on `window`. This is the
|
|
8
|
-
* canonical pattern for chunk-load failures: a missing static bundle
|
|
9
|
-
* chunk results in a non-functional app with no JS exception.
|
|
10
|
-
*/
|
|
11
1
|
import { type SafetyEventReporter } from './ports.js';
|
|
12
2
|
export interface ResourceErrorObserverOptions {
|
|
13
3
|
reporter?: SafetyEventReporter | undefined;
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* canonical pattern for chunk-load failures: a missing static bundle
|
|
9
9
|
* chunk results in a non-functional app with no JS exception.
|
|
10
10
|
*/
|
|
11
|
+
import { telemetryUrl } from './telemetry-url.js';
|
|
11
12
|
import { noopSafetyEventReporter } from './ports.js';
|
|
12
13
|
const RESOURCE_TAGS = new Set(['SCRIPT', 'LINK', 'IMG', 'IFRAME', 'AUDIO', 'VIDEO', 'SOURCE', 'TRACK']);
|
|
13
14
|
let installed = false;
|
|
@@ -29,8 +30,8 @@ export function installResourceErrorObserver(options = {}) {
|
|
|
29
30
|
return;
|
|
30
31
|
if (!RESOURCE_TAGS.has(target.tagName))
|
|
31
32
|
return;
|
|
32
|
-
const src = readSrc(target);
|
|
33
|
-
if (src
|
|
33
|
+
const src = telemetryUrl({ value: readSrc(target) });
|
|
34
|
+
if (typeof src !== 'string' || src.length === 0)
|
|
34
35
|
return;
|
|
35
36
|
reporter('client_resource_error', {
|
|
36
37
|
tag: target.tagName.toLowerCase(),
|
|
@@ -50,16 +51,20 @@ export function installResourceErrorObserver(options = {}) {
|
|
|
50
51
|
};
|
|
51
52
|
}
|
|
52
53
|
function readSrc(el) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (
|
|
62
|
-
return
|
|
63
|
-
|
|
54
|
+
if (el instanceof HTMLLinkElement)
|
|
55
|
+
return el.href;
|
|
56
|
+
if (el instanceof HTMLScriptElement)
|
|
57
|
+
return el.src;
|
|
58
|
+
if (el instanceof HTMLImageElement)
|
|
59
|
+
return el.src;
|
|
60
|
+
if (el instanceof HTMLIFrameElement)
|
|
61
|
+
return el.src;
|
|
62
|
+
if (el instanceof HTMLSourceElement)
|
|
63
|
+
return el.src;
|
|
64
|
+
if (el instanceof HTMLTrackElement)
|
|
65
|
+
return el.src;
|
|
66
|
+
if (el instanceof HTMLMediaElement)
|
|
67
|
+
return el.src;
|
|
68
|
+
return el.getAttribute('src') ?? el.getAttribute('href');
|
|
64
69
|
}
|
|
65
70
|
//# sourceMappingURL=resource-error.js.map
|
|
@@ -79,8 +79,8 @@ function scheduleEmit(runId, stuckAfterMs) {
|
|
|
79
79
|
return setTimeout(() => emitStuck(runId), stuckAfterMs);
|
|
80
80
|
}
|
|
81
81
|
function emitStuck(runId) {
|
|
82
|
-
|
|
83
|
-
//
|
|
82
|
+
// Asserted present: there is no `!entry || entry.emitted` guard here (removed in the 2026-10-21
|
|
83
|
+
// coverage pass). Such a guard was a defensive invariant check, empirically
|
|
84
84
|
// and structurally unreachable through this module's real call graph
|
|
85
85
|
// today: `emitStuck` only ever runs as a `setTimeout` callback created by
|
|
86
86
|
// `scheduleEmit`, and every path that could remove or re-flag an entry
|
|
@@ -88,13 +88,17 @@ function emitStuck(runId) {
|
|
|
88
88
|
// `clearTimeout`s that exact timer first — in this single-threaded
|
|
89
89
|
// runtime, a cleared timer's callback can never still fire. So by the
|
|
90
90
|
// time this callback runs, its entry is guaranteed to both exist and
|
|
91
|
-
// have `emitted === false`.
|
|
91
|
+
// have `emitted === false`. Unreachable code is refactored away rather than fake-tested — see
|
|
92
|
+
// packages/ui/archived provenance ledger's 2026-07-22 dated entry for the full proof.
|
|
93
|
+
//
|
|
94
|
+
// Original rationale for the removed guard, kept verbatim (moved, not deleted) so the trade-off
|
|
95
|
+
// stays visible to whoever revisits it: "Left in place (not stripped) as a guard
|
|
92
96
|
// against a future refactor silently breaking that invariant — see
|
|
93
97
|
// packages/ui/archived provenance ledger's 2026-07-22 dated entry for the full proof,
|
|
94
98
|
// matching this task's own standard for documenting provable
|
|
95
|
-
// unreachability instead of forcing a test.
|
|
96
|
-
|
|
97
|
-
|
|
99
|
+
// unreachability instead of forcing a test." A refactor that lets a cleared or re-flagged
|
|
100
|
+
// entry reach this callback must therefore restore that guard here.
|
|
101
|
+
const entry = runs.get(runId);
|
|
98
102
|
entry.emitted = true;
|
|
99
103
|
entry.reporter('client_run_stuck', {
|
|
100
104
|
run_id: runId,
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Returns an HTTP(S) URL's origin/path, or a relative pathname, with no credentials/query/fragment.
|
|
2
|
+
* Invalid and opaque URLs are omitted. O(n) time/space in input length. */
|
|
3
|
+
export declare function telemetryUrl({ value }: {
|
|
4
|
+
value: unknown;
|
|
5
|
+
}, _options?: {}): string | undefined;
|
|
6
|
+
//# sourceMappingURL=telemetry-url.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Returns an HTTP(S) URL's origin/path, or a relative pathname, with no credentials/query/fragment.
|
|
2
|
+
* Invalid and opaque URLs are omitted. O(n) time/space in input length. */
|
|
3
|
+
export function telemetryUrl({ value }, _options = {}) {
|
|
4
|
+
if (typeof value !== 'string' || value.length === 0 || /\s/.test(value))
|
|
5
|
+
return undefined;
|
|
6
|
+
try {
|
|
7
|
+
const parsed = new URL(value, 'https://telemetry.invalid/');
|
|
8
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')
|
|
9
|
+
return undefined;
|
|
10
|
+
return /^[a-z][a-z0-9+.-]*:|^\/\//i.test(value) ? `${parsed.origin}${parsed.pathname}` : parsed.pathname;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=telemetry-url.js.map
|
|
@@ -55,26 +55,23 @@ export function installWhiteScreenDetector(options = {}) {
|
|
|
55
55
|
minVisibleText,
|
|
56
56
|
rootElementId,
|
|
57
57
|
});
|
|
58
|
-
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
// function's real call graph: both places that ever set
|
|
58
|
+
// There is no `cancelled` flag guarding the timer callback or the
|
|
59
|
+
// `monitorMount` completion callback just below. Such guards were defensive
|
|
60
|
+
// invariant checks, empirically and structurally unreachable through this
|
|
61
|
+
// function's real call graph: both places that ever set the old
|
|
63
62
|
// `cancelled = true` (this closure's returned teardown, and the
|
|
64
63
|
// `monitorMount` completion callback itself) always call
|
|
65
64
|
// `window.clearTimeout(timer)` / `observer.disconnect()` synchronously in
|
|
66
65
|
// that same turn — and in this single-threaded runtime, a timer that's
|
|
67
66
|
// been cleared (or a `MutationObserver` that's been disconnected, which
|
|
68
67
|
// also discards its pending record queue per spec) can never still fire
|
|
69
|
-
// its callback afterward. So
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
// dated entry for the full proof, same
|
|
74
|
-
// `stuck-run.ts` `emitStuck
|
|
68
|
+
// its callback afterward. So neither callback can run after teardown or
|
|
69
|
+
// after the other has finished. The guards (and the then write-only flag)
|
|
70
|
+
// were removed in the 2026-10-21 coverage pass — unreachable code is
|
|
71
|
+
// refactored away rather than fake-tested; see packages/ui/archived
|
|
72
|
+
// provenance ledger's 2026-07-22 dated entry for the full proof, same
|
|
73
|
+
// pattern as this package's `stuck-run.ts` `emitStuck`.
|
|
75
74
|
const timer = window.setTimeout(() => {
|
|
76
|
-
if (cancelled)
|
|
77
|
-
return;
|
|
78
75
|
if (isAppMounted())
|
|
79
76
|
return;
|
|
80
77
|
reporter('client_white_screen', {
|
|
@@ -89,13 +86,9 @@ export function installWhiteScreenDetector(options = {}) {
|
|
|
89
86
|
});
|
|
90
87
|
}, timeoutMs);
|
|
91
88
|
const stopMonitor = monitorMount(isAppMounted, () => {
|
|
92
|
-
if (cancelled)
|
|
93
|
-
return;
|
|
94
|
-
cancelled = true;
|
|
95
89
|
window.clearTimeout(timer);
|
|
96
90
|
});
|
|
97
91
|
return () => {
|
|
98
|
-
cancelled = true;
|
|
99
92
|
window.clearTimeout(timer);
|
|
100
93
|
stopMonitor();
|
|
101
94
|
};
|
|
@@ -141,8 +134,8 @@ function isLoadingShell(el, loadingShellClasses) {
|
|
|
141
134
|
return false;
|
|
142
135
|
}
|
|
143
136
|
function monitorMount(isAppMounted, onMounted) {
|
|
144
|
-
|
|
145
|
-
//
|
|
137
|
+
// There is no `stopped` flag or guard at the top of this callback (both
|
|
138
|
+
// removed in the 2026-10-21 coverage pass) — the guard was a defensive
|
|
146
139
|
// invariant check, empirically confirmed unreachable: a `MutationObserver`
|
|
147
140
|
// callback fires at most once per batch of queued mutation records
|
|
148
141
|
// (never re-entrantly mid-callback), `stopped` only ever flips to `true`
|
|
@@ -157,10 +150,7 @@ function monitorMount(isAppMounted, onMounted) {
|
|
|
157
150
|
// zero more. See packages/ui/archived provenance ledger's 2026-07-22 dated entry for
|
|
158
151
|
// the full record.
|
|
159
152
|
const observer = new MutationObserver(() => {
|
|
160
|
-
if (stopped)
|
|
161
|
-
return;
|
|
162
153
|
if (isAppMounted()) {
|
|
163
|
-
stopped = true;
|
|
164
154
|
observer.disconnect();
|
|
165
155
|
onMounted();
|
|
166
156
|
}
|
|
@@ -178,12 +168,10 @@ function monitorMount(isAppMounted, onMounted) {
|
|
|
178
168
|
// Best-effort short-circuit: if the app is already mounted by the time
|
|
179
169
|
// this runs (HMR, slow tab, etc.), fire immediately.
|
|
180
170
|
if (isAppMounted()) {
|
|
181
|
-
stopped = true;
|
|
182
171
|
observer.disconnect();
|
|
183
172
|
onMounted();
|
|
184
173
|
}
|
|
185
174
|
return () => {
|
|
186
|
-
stopped = true;
|
|
187
175
|
observer.disconnect();
|
|
188
176
|
};
|
|
189
177
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import { type FetchQueryEnvironmentPort } from '@jini-ai/core/fetch';
|
|
3
|
-
import type { CachedLoader, CachedLoaderOptions, FetchMutationOptions, FetchQueryOptions, MutationResult, QueryKey, QueryResult } from '@jini-ai/core/fetch';
|
|
3
|
+
import type { CachedLoader, CachedLoaderDefaults, CachedLoaderOptions, FetchMutationOptions, FetchQueryOptions, MutationResult, QueryKey, QueryResult } from '@jini-ai/core/fetch';
|
|
4
4
|
/** Isolated cache scope; no external provider or query-library singleton is needed. */
|
|
5
5
|
export declare function FetchQueryProvider({ children, environment }: {
|
|
6
6
|
children: ReactNode;
|
|
@@ -27,7 +27,7 @@ export declare function useFetchQuery<T>(required: Pick<FetchQueryOptions<T>, "k
|
|
|
27
27
|
/** A write returns its rejection to awaiting callers and also handles discarded promises. */
|
|
28
28
|
export declare function useFetchMutation<TInput, TOutput>(required: Pick<FetchMutationOptions<TInput, TOutput>, "run">, optional?: Omit<FetchMutationOptions<TInput, TOutput>, "run">): MutationResult<TInput, TOutput>;
|
|
29
29
|
/** Promise loader sharing the provider cache; replace wins over an older in-flight read. */
|
|
30
|
-
export declare function useCachedLoader<T>({ key, fetch }:
|
|
30
|
+
export declare function useCachedLoader<T>({ key, fetch }: CachedLoaderDefaults<T>, { staleTime }?: Omit<CachedLoaderOptions<T>, "key" | "fetch">): CachedLoader<T>;
|
|
31
31
|
/** Prefix invalidation refreshes every currently enabled read under that key. */
|
|
32
32
|
export declare function useInvalidate(): (required: {
|
|
33
33
|
key: QueryKey;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
|
|
3
|
-
import { DEFAULT_STALE_TIME, FetchQueryCache, toQueryError } from '@jini-ai/core/fetch';
|
|
3
|
+
import { DEFAULT_STALE_TIME, FetchQueryCache, toQueryError, withCachedLoaderFetch } from '@jini-ai/core/fetch';
|
|
4
4
|
import { browserFetchQueryEnvironment } from './environment.browser.js';
|
|
5
5
|
import { FetchQueryBinding } from './binding.react.js';
|
|
6
6
|
const CacheContext = createContext(null);
|
|
@@ -109,7 +109,9 @@ export function useCachedLoader({ key, fetch }, { staleTime = DEFAULT_STALE_TIME
|
|
|
109
109
|
const cache = useCache();
|
|
110
110
|
return useMemo(() => ({
|
|
111
111
|
peek: () => cache.entry({ key: key }, { staleTime: staleTime }).state.data,
|
|
112
|
-
|
|
112
|
+
// `fetch` is the loader's optional default; `withCachedLoaderFetch` rejects an argument-less read
|
|
113
|
+
// that has none before the cache is touched (see `CachedLoaderDefaults`).
|
|
114
|
+
load: (required = { key, fetch }, { staleTime: readStaleTime = staleTime } = {}) => withCachedLoaderFetch(required, read => cache.load({ entry: cache.entry({ key: read.key }, { staleTime }), fetch: read.fetch, staleTime: readStaleTime })),
|
|
113
115
|
replace: ({ value }) => cache.replace({ entry: cache.entry({ key: key }, { staleTime: staleTime }), data: value }),
|
|
114
116
|
}), [cache, key, fetch, staleTime]);
|
|
115
117
|
}
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
/** Optional TanStack translation layer. No library types escape the fetch-query contract. */
|
|
3
3
|
import { createContext, useCallback, useContext, useEffect, useMemo, useSyncExternalStore } from 'react';
|
|
4
4
|
import { isCancelledError, replaceEqualDeep, QueryClient, QueryClientProvider, useMutation, useQuery } from '@tanstack/react-query';
|
|
5
|
-
import { DEFAULT_STALE_TIME, FetchQueryCache, IDLE_RETENTION, toQueryError } from '@jini-ai/core/fetch';
|
|
5
|
+
import { DEFAULT_STALE_TIME, FetchQueryCache, IDLE_RETENTION, toQueryError, withCachedLoaderFetch } from '@jini-ai/core/fetch';
|
|
6
6
|
import { browserFetchQueryEnvironment } from './environment.browser.js';
|
|
7
7
|
import { resolveFetchQueryError, resolveFetchQueryStatus } from './adapter.react.js';
|
|
8
8
|
import { FetchQueryBinding } from './binding.react.js';
|
|
@@ -42,12 +42,12 @@ export function FetchQueryProvider({ children, environment = browserFetchQueryEn
|
|
|
42
42
|
}, [scope, environment]);
|
|
43
43
|
return _jsx(QueryClientProvider, { client: scope.client, children: _jsx(ScopeContext.Provider, { value: scope, children: _jsx(FetchQueryBinding, { adapter: adapter, children: children }) }) });
|
|
44
44
|
}
|
|
45
|
-
/**
|
|
45
|
+
/** The provider's scope. This adapter's hooks are not exported: the only route to them is the
|
|
46
|
+
* `adapter` object this module's own `FetchQueryProvider` binds INSIDE its `ScopeContext`, and the
|
|
47
|
+
* public hooks already fail closed without a provider (`binding.react.tsx`'s `useAdapter`). So the
|
|
48
|
+
* old "no scope" throw here was unreachable and is gone; the guarantee is that binding order. */
|
|
46
49
|
function useScope() {
|
|
47
|
-
|
|
48
|
-
if (!scope)
|
|
49
|
-
throw new Error('Fetch-query hooks require FetchQueryProvider');
|
|
50
|
-
return scope;
|
|
50
|
+
return useContext(ScopeContext);
|
|
51
51
|
}
|
|
52
52
|
/** Observe only this provider's connectivity; paused reads do not claim network activity. */
|
|
53
53
|
function useOnline({ environment }) {
|
|
@@ -174,40 +174,53 @@ function useFetchMutation({ run }, { invalidates } = {}) {
|
|
|
174
174
|
}, [mutateAsync]);
|
|
175
175
|
return { mutate, status: mutation.status, error: mutation.status === 'error' ? toQueryError(mutation.error) : null, reset };
|
|
176
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* One loader read of `required.key`, unwrapped to its value. Lifted out of `useCachedLoader`'s `load`
|
|
179
|
+
* verbatim so the missing-fetch guard (`withCachedLoaderFetch`) wrapping it does not push its
|
|
180
|
+
* callbacks past the function-nesting limit.
|
|
181
|
+
*/
|
|
182
|
+
function loadLoaderQuery({ scope, options, required, staleTime }) {
|
|
183
|
+
const selected = { ...options, staleTime, queryKey: required.key, queryFn: () => read({ scope, key: required.key, fetch: required.fetch }),
|
|
184
|
+
structuralSharing: (previous, incoming) => saveResult({ scope, key: required.key, previous, incoming }) };
|
|
185
|
+
return scope.client.fetchQuery(selected).catch(error => {
|
|
186
|
+
// A forced refresh can supersede a loader's request without aborting its transport.
|
|
187
|
+
// Follow the new request instead of exposing TanStack's cancellation vocabulary.
|
|
188
|
+
if (!isCancelledError(error))
|
|
189
|
+
throw error;
|
|
190
|
+
// Invalidation also supersedes loader-only reads. Join (or start) the post-write
|
|
191
|
+
// request rather than returning an obsolete value while the key is invalidated.
|
|
192
|
+
const query = scope.client.getQueryCache().find({ queryKey: required.key, exact: true });
|
|
193
|
+
if (query.state.isInvalidated)
|
|
194
|
+
return scope.client.fetchQuery(selected);
|
|
195
|
+
return (query.promise ?? scope.client.getQueryData(required.key));
|
|
196
|
+
}).then(result => {
|
|
197
|
+
const query = scope.client.getQueryCache().find({ queryKey: required.key, exact: true });
|
|
198
|
+
const history = historyFor({ scope, query });
|
|
199
|
+
// Query.fetch returns the transport envelope, not structuralSharing's accepted value.
|
|
200
|
+
// Unwrap against the current generation so awaiting loaders agree with observers.
|
|
201
|
+
if (result.generation !== history.replacement)
|
|
202
|
+
return scope.client.getQueryData(required.key).value;
|
|
203
|
+
if ('error' in result)
|
|
204
|
+
throw result.error;
|
|
205
|
+
return result.value;
|
|
206
|
+
});
|
|
207
|
+
}
|
|
177
208
|
/** Loader shares reactive reads, retains fresh idle values, and protects replacement writes. */
|
|
178
209
|
function useCachedLoader({ key, fetch }, { staleTime = DEFAULT_STALE_TIME } = {}) {
|
|
179
210
|
const scope = useScope();
|
|
180
211
|
return useMemo(() => {
|
|
181
|
-
|
|
212
|
+
// The template every loader query starts from. It carries no `queryFn`: `load` always supplies
|
|
213
|
+
// its own (for `required.key`), an observer that later mounts on a `replace`-created query
|
|
214
|
+
// installs the observer's, and an unobserved query is never refetched — so a template `queryFn`
|
|
215
|
+
// here could never run. Its `structuralSharing` does run: `replace` on a cold key builds the query
|
|
216
|
+
// from this template and publishes through it.
|
|
217
|
+
const options = { queryKey: key,
|
|
182
218
|
structuralSharing: (previous, incoming) => saveResult({ scope, key, previous, incoming }), staleTime, gcTime: gcTimeFor({ staleTime }) };
|
|
183
219
|
return {
|
|
184
220
|
peek: () => scope.client.getQueryData(key)?.value,
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
return scope.client.fetchQuery(selected).catch(error => {
|
|
189
|
-
// A forced refresh can supersede a loader's request without aborting its transport.
|
|
190
|
-
// Follow the new request instead of exposing TanStack's cancellation vocabulary.
|
|
191
|
-
if (!isCancelledError(error))
|
|
192
|
-
throw error;
|
|
193
|
-
// Invalidation also supersedes loader-only reads. Join (or start) the post-write
|
|
194
|
-
// request rather than returning an obsolete value while the key is invalidated.
|
|
195
|
-
const query = scope.client.getQueryCache().find({ queryKey: required.key, exact: true });
|
|
196
|
-
if (query.state.isInvalidated)
|
|
197
|
-
return scope.client.fetchQuery(selected);
|
|
198
|
-
return (query.promise ?? scope.client.getQueryData(required.key));
|
|
199
|
-
}).then(result => {
|
|
200
|
-
const query = scope.client.getQueryCache().find({ queryKey: required.key, exact: true });
|
|
201
|
-
const history = historyFor({ scope, query });
|
|
202
|
-
// Query.fetch returns the transport envelope, not structuralSharing's accepted value.
|
|
203
|
-
// Unwrap against the current generation so awaiting loaders agree with observers.
|
|
204
|
-
if (result.generation !== history.replacement)
|
|
205
|
-
return scope.client.getQueryData(required.key).value;
|
|
206
|
-
if ('error' in result)
|
|
207
|
-
throw result.error;
|
|
208
|
-
return result.value;
|
|
209
|
-
});
|
|
210
|
-
},
|
|
221
|
+
// `fetch` is the loader's optional default; `withCachedLoaderFetch` rejects an argument-less read
|
|
222
|
+
// that has none before any query is built (see `CachedLoaderDefaults`).
|
|
223
|
+
load: (requested = { key, fetch }, { staleTime: readStaleTime = staleTime } = {}) => withCachedLoaderFetch(requested, required => loadLoaderQuery({ scope, options, required, staleTime: readStaleTime })),
|
|
211
224
|
replace: ({ value }) => {
|
|
212
225
|
const query = scope.client.getQueryCache().build(scope.client, options);
|
|
213
226
|
const history = historyFor({ scope, query });
|
|
@@ -26,8 +26,10 @@ export const LOCAL_BACKUP_PRINCIPAL_ID = "local-tab-backup";
|
|
|
26
26
|
* @complexity O(draft size) for reads/writes; O(1) for clears.
|
|
27
27
|
*/
|
|
28
28
|
export function createStandingDraftLocalBackup({ keyPrefix: KEY_PREFIX, getStorage }, _options = {}) {
|
|
29
|
+
// Never sees a `null` principal: write/read/clear each return before reaching storage for one, so
|
|
30
|
+
// the old `principalId ?? ""` fallback here could not run.
|
|
29
31
|
function keyFor(entryId, principalId) {
|
|
30
|
-
return principalId === undefined ? `${KEY_PREFIX}${entryId}` : `${KEY_PREFIX}${encodeURIComponent(principalId
|
|
32
|
+
return principalId === undefined ? `${KEY_PREFIX}${entryId}` : `${KEY_PREFIX}${encodeURIComponent(principalId)}.${encodeURIComponent(entryId)}`;
|
|
31
33
|
}
|
|
32
34
|
/** Mirrors a refused draft for this browser profile. Silent no-op on any storage failure.
|
|
33
35
|
* @complexity Time/space: O(size of the draft) for the one `JSON.stringify`. */
|
|
@@ -261,7 +261,9 @@ export function useSettingsSlice(options, _optional = {}) {
|
|
|
261
261
|
// left to paint. The write itself still has to happen — through the same
|
|
262
262
|
// lane as the debounce path, so it diffs against the base that is
|
|
263
263
|
// current when it RUNS, not the one that was current when it was queued.
|
|
264
|
-
void writes.run({ task: () => runSave().then(() => { },
|
|
264
|
+
void writes.run({ task: () => runSave().then(() => { },
|
|
265
|
+
// No view remains to show the failure; settings and native errors may contain secrets.
|
|
266
|
+
() => { console.error('[settings] Final save failed after unmount.'); }) });
|
|
265
267
|
}, [runSave, writes]);
|
|
266
268
|
const onChange = useCallback((next) => {
|
|
267
269
|
setValue(next);
|