@jini-ai/ui 0.3.14 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +139 -0
- package/README.md +120 -26
- package/dist/browser/index.js +1 -1
- package/dist/browser/useDismissOnOutsideOrEscape.d.ts +10 -1
- package/dist/browser/useDismissOnOutsideOrEscape.js +7 -4
- package/dist/browser/useGlobalKeydown.d.ts +14 -2
- package/dist/browser/useGlobalKeydown.js +8 -17
- package/dist/features/a2ui/catalog-from-registry.d.ts +6 -3
- package/dist/features/a2ui/catalog-from-registry.js +1 -1
- package/dist/features/a2ui/index.d.ts +1 -1
- package/dist/features/a2ui/protocol.d.ts +1 -1
- package/dist/features/a2ui/renderer.js +23 -10
- package/dist/features/a2ui/use-a2ui-surface.d.ts +11 -6
- package/dist/features/a2ui/use-a2ui-surface.js +40 -10
- package/dist/features/admin-widgets/components/ComingSoonNotice.d.ts +11 -0
- package/dist/features/admin-widgets/components/ComingSoonNotice.js +7 -0
- package/dist/features/admin-widgets/components/ComingSoonPanel.d.ts +10 -0
- package/dist/features/admin-widgets/components/ComingSoonPanel.js +10 -0
- package/dist/features/admin-widgets/components/ImagePreviewModal.d.ts +16 -0
- package/dist/features/admin-widgets/components/ImagePreviewModal.hooks.d.ts +13 -0
- package/dist/features/admin-widgets/components/ImagePreviewModal.hooks.js +46 -0
- package/dist/features/admin-widgets/components/ImagePreviewModal.js +13 -0
- package/dist/features/admin-widgets/components/InfoTip.d.ts +18 -0
- package/dist/features/admin-widgets/components/InfoTip.hooks.d.ts +16 -0
- package/dist/features/admin-widgets/components/InfoTip.hooks.js +45 -0
- package/dist/features/admin-widgets/components/InfoTip.js +12 -0
- package/dist/features/admin-widgets/components/SeeMore/SeeMore.d.ts +41 -0
- package/dist/features/admin-widgets/components/SeeMore/SeeMore.hooks.d.ts +14 -0
- package/dist/features/admin-widgets/components/SeeMore/SeeMore.hooks.js +57 -0
- package/dist/features/admin-widgets/components/SeeMore/SeeMore.js +24 -0
- package/dist/features/admin-widgets/components/Select/Select.d.ts +39 -0
- package/dist/features/admin-widgets/components/Select/Select.hooks.d.ts +105 -0
- package/dist/features/admin-widgets/components/Select/Select.hooks.js +359 -0
- package/dist/features/admin-widgets/components/Select/Select.js +46 -0
- package/dist/features/admin-widgets/index.d.ts +20 -0
- package/dist/features/admin-widgets/index.js +11 -0
- package/dist/features/admin-widgets/styles/admin-widgets.css +2 -0
- package/dist/features/appearance/constants.d.ts +2 -2
- package/dist/features/appearance/constants.js +2 -1
- package/dist/features/appearance/react/components/AppearanceTab.js +5 -5
- package/dist/features/appearance/rules.d.ts +10 -4
- package/dist/features/appearance/rules.js +5 -5
- package/dist/features/appearance/types.d.ts +1 -1
- package/dist/features/appearance/types.js +1 -1
- package/dist/features/asset-grid/react/components/AssetGrid.js +1 -1
- package/dist/features/asset-grid/react/components/DeleteConfirmDialog.js +1 -1
- package/dist/features/asset-grid/react/hooks/useAssetGridKeyboardShortcuts.js +23 -21
- package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.d.ts +1 -1
- package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.js +1 -1
- package/dist/features/asset-tree-browser/ports.d.ts +3 -1
- package/dist/features/asset-tree-browser/react/hooks/useAssetTreeCopyLocalPath.js +1 -1
- package/dist/features/browser-chrome/react/components/BrowserViewportControls.js +1 -1
- package/dist/features/browser-chrome/types.js +1 -1
- package/dist/features/command-palette/ports.js +1 -1
- package/dist/features/connectors/ConnectorsBrowser.js +2 -2
- package/dist/features/connectors/components/ConnectorCard.js +3 -3
- package/dist/features/connectors/components/ConnectorDetailDrawer.js +1 -1
- package/dist/features/connectors/components/ConnectorGrid.js +2 -2
- package/dist/features/connectors/components/ConnectorSearchBar.js +2 -2
- package/dist/features/connectors/components/ProviderTabBar.js +2 -2
- package/dist/features/connectors/constants.d.ts +1 -1
- package/dist/features/connectors/constants.js +1 -1
- package/dist/features/connectors/hooks/useConnectorAuthorization.js +1 -1
- package/dist/features/connectors/rules.d.ts +2 -2
- package/dist/features/connectors/rules.js +1 -1
- package/dist/features/connectors/types.d.ts +2 -2
- package/dist/features/connectors/types.js +1 -1
- package/dist/features/execution/react/components/ByokProviderForm.js +3 -3
- package/dist/features/execution/react/components/ExecutionTab.js +2 -2
- package/dist/features/execution/react/components/LocalCliAgentCard.js +3 -3
- package/dist/features/execution/react/components/LocalCliAgentList.js +2 -2
- package/dist/features/execution/react/components/ProviderChipGroup.js +2 -2
- package/dist/features/execution/react/components/SearchableModelSelect.js +1 -1
- package/dist/features/execution/rules.js +1 -1
- package/dist/features/file-dropzone/react/components/FileDropzone.js +5 -3
- package/dist/features/file-dropzone/react/hooks/useFileDropzone.d.ts +1 -1
- package/dist/features/file-dropzone/react/hooks/useFileDropzone.js +1 -1
- package/dist/features/file-dropzone/types.d.ts +1 -1
- package/dist/features/file-dropzone/types.js +1 -1
- package/dist/features/folder-path-drop/react/hooks/useFolderPathDropCapture.js +6 -4
- package/dist/features/html-editor/canvas-content-wrapper.js +1 -1
- package/dist/features/html-editor/canvas-embed-placeholders.js +7 -7
- package/dist/features/html-editor/canvas-style.d.ts +4 -0
- package/dist/features/html-editor/canvas-style.js +6 -21
- package/dist/features/html-viewer/constants.d.ts +1 -1
- package/dist/features/html-viewer/constants.js +1 -1
- package/dist/features/html-viewer/react/components/PresentMenu.js +1 -1
- package/dist/features/html-viewer/react/components/ZoomMenu.js +1 -1
- package/dist/features/html-viewer/types.d.ts +1 -1
- package/dist/features/html-viewer/types.js +1 -1
- package/dist/features/i18n/dictionaries/index.d.ts +2 -3
- package/dist/features/i18n/dictionaries/settings-dialog.en.d.ts +1 -1
- package/dist/features/i18n/dictionary-translator.d.ts +16 -0
- package/dist/features/i18n/dictionary-translator.js +14 -0
- package/dist/features/i18n/index.d.ts +3 -0
- package/dist/features/i18n/index.js +2 -0
- package/dist/features/i18n/locale.d.ts +1 -1
- package/dist/features/i18n/template-i18n.d.ts +26 -0
- package/dist/features/i18n/template-i18n.js +36 -0
- package/dist/features/iframe-pool/react/components/IframeKeepAliveProvider.d.ts +1 -1
- package/dist/features/iframe-pool/react/components/IframeKeepAliveProvider.js +1 -1
- package/dist/features/instructions/react/components/InstructionsTab.js +1 -1
- package/dist/features/integrations/react/components/ClientPicker.js +4 -4
- package/dist/features/integrations/react/components/CodexInstallToggleButton.js +1 -1
- package/dist/features/integrations/react/components/IntegrationsTab.js +2 -2
- package/dist/features/integrations/react/components/SnippetBlock.js +1 -1
- package/dist/features/integrations/types.d.ts +1 -1
- package/dist/features/integrations/types.js +1 -1
- package/dist/features/interactive-ui/index.js +13 -13
- package/dist/features/interactive-ui/interactive-ui.css +1 -1
- package/dist/features/interactive-ui/providers/recharts/bar-chart.js +1 -1
- package/dist/features/interactive-ui/providers/recharts/line-chart.js +1 -1
- package/dist/features/interactive-ui/providers/recharts/pie-chart.js +2 -2
- package/dist/features/interactive-ui/providers/shadcn/lib/utils.d.ts +1 -1
- package/dist/features/interactive-ui/providers/shadcn/lib/utils.js +1 -1
- package/dist/features/interactive-ui/registry.d.ts +15 -4
- package/dist/features/interactive-ui/registry.js +5 -5
- package/dist/features/language/react/components/LanguageTab.js +2 -2
- package/dist/features/list-detail-panel/types.d.ts +1 -1
- package/dist/features/list-detail-panel/types.js +1 -1
- package/dist/features/mcp-ui/confirmation-store.js +2 -1
- package/dist/features/mcp-ui/surfaces/tokens.d.ts +17 -20
- package/dist/features/mcp-ui/surfaces/tokens.js +33 -35
- package/dist/features/media-providers/index.d.ts +1 -1
- package/dist/features/media-providers/index.js +1 -1
- package/dist/features/media-providers/react/components/MediaProvidersTab.js +4 -4
- package/dist/features/media-providers/rules.js +1 -1
- package/dist/features/memory/async-commit-guard.d.ts +1 -1
- package/dist/features/memory/constants.d.ts +1 -1
- package/dist/features/memory/constants.js +1 -1
- package/dist/features/memory/dependencies.d.ts +11 -23
- package/dist/features/memory/dependencies.js +116 -106
- package/dist/features/memory/index.d.ts +1 -1
- package/dist/features/memory/index.js +1 -1
- package/dist/features/memory/react/components/MemoryAdvancedModal.js +2 -2
- package/dist/features/memory/react/components/MemoryEntryCard.js +1 -1
- package/dist/features/memory/react/components/MemoryExtractionCard.js +1 -1
- package/dist/features/memory/react/components/MemoryHooksPanel.js +1 -1
- package/dist/features/memory/react/components/MemoryList.js +5 -5
- package/dist/features/memory/react/components/MemoryManualEditor.js +5 -5
- package/dist/features/memory/react/components/MemorySettingsPanel.js +1 -1
- package/dist/features/memory/react/hooks/useMemoryEntries.hooks.js +1 -1
- package/dist/features/memory/react/hooks/useMemoryNavigation.hooks.js +1 -1
- package/dist/features/memory/react/styles.js +1 -1
- package/dist/features/memory/rules.js +1 -1
- package/dist/features/memory/types.d.ts +1 -1
- package/dist/features/mention-autocomplete/react/hooks/useMentionAutocomplete.js +1 -1
- package/dist/features/notifications/react/components/NotificationsTab.js +7 -7
- package/dist/features/notifications/types.d.ts +1 -1
- package/dist/features/observability/boot-timing.js +2 -2
- package/dist/features/observability/stuck-run.js +1 -1
- package/dist/features/observability/white-screen.js +4 -4
- package/dist/features/panel-kit/fetch-query/adapter.react.d.ts +35 -0
- package/dist/features/panel-kit/fetch-query/adapter.react.js +122 -0
- package/dist/features/panel-kit/fetch-query/cache.d.ts +123 -0
- package/dist/features/panel-kit/fetch-query/cache.js +231 -0
- package/dist/features/panel-kit/fetch-query/environment.browser.d.ts +4 -0
- package/dist/features/panel-kit/fetch-query/environment.browser.js +25 -0
- package/dist/features/panel-kit/fetch-query/index.d.ts +4 -0
- package/dist/features/panel-kit/fetch-query/index.js +2 -0
- package/dist/features/panel-kit/fetch-query/types.d.ts +91 -0
- package/dist/features/panel-kit/fetch-query/types.js +2 -0
- package/dist/features/panel-kit/helpers/format-timestamp.d.ts +18 -0
- package/dist/features/panel-kit/helpers/format-timestamp.js +20 -0
- package/dist/features/panel-kit/helpers/permissions.d.ts +11 -0
- package/dist/features/panel-kit/helpers/permissions.js +10 -0
- package/dist/features/panel-kit/helpers/resolve-active-tab-id.d.ts +13 -0
- package/dist/features/panel-kit/helpers/resolve-active-tab-id.js +13 -0
- package/dist/features/panel-kit/helpers/retry-unreachable.d.ts +20 -0
- package/dist/features/panel-kit/helpers/retry-unreachable.js +39 -0
- package/dist/features/panel-kit/hooks/use-async-action.hooks.d.ts +13 -0
- package/dist/features/panel-kit/hooks/use-async-action.hooks.js +27 -0
- package/dist/features/panel-kit/hooks/use-dirty-guard.hooks.d.ts +18 -0
- package/dist/features/panel-kit/hooks/use-dirty-guard.hooks.js +33 -0
- package/dist/features/panel-kit/hooks/use-focus-trap.hooks.d.ts +8 -0
- package/dist/features/panel-kit/hooks/use-focus-trap.hooks.js +66 -0
- package/dist/features/panel-kit/hooks/use-serial-writes.hooks.d.ts +26 -0
- package/dist/features/panel-kit/hooks/use-serial-writes.hooks.js +41 -0
- package/dist/features/panel-kit/hooks/use-settlement-generation.hooks.d.ts +9 -0
- package/dist/features/panel-kit/hooks/use-settlement-generation.hooks.js +25 -0
- package/dist/features/panel-kit/index.d.ts +19 -0
- package/dist/features/panel-kit/index.js +13 -0
- package/dist/features/privacy/react/components/PrivacyTab.js +2 -2
- package/dist/features/privacy/types.d.ts +1 -1
- package/dist/features/privacy/types.js +1 -1
- package/dist/features/progress-card/reference-adapters.d.ts +1 -1
- package/dist/features/progress-card/rules.d.ts +1 -1
- package/dist/features/progress-card/rules.js +1 -1
- package/dist/features/progress-card/types.d.ts +1 -1
- package/dist/features/progress-card/types.js +1 -1
- package/dist/features/resource-dashboard/dependencies.d.ts +1 -1
- package/dist/features/resource-dashboard/ports.d.ts +1 -1
- package/dist/features/resource-dashboard/react/hooks/useResourceBoard.d.ts +1 -1
- package/dist/features/resource-dashboard/react/hooks/useResourceBoard.js +1 -1
- package/dist/features/resource-dashboard/rules.d.ts +1 -1
- package/dist/features/resource-dashboard/rules.js +2 -2
- package/dist/features/resource-dashboard/types.d.ts +4 -4
- package/dist/features/resource-dashboard/types.js +1 -1
- package/dist/features/schedule-picker/react/hooks/useRecurringSchedulePicker.js +1 -1
- package/dist/features/settings/dialog/styles/settings-dialog.css +62 -60
- package/dist/features/sketch-editor/constants.d.ts +1 -1
- package/dist/features/sketch-editor/constants.js +1 -1
- package/dist/features/sketch-editor/dom.d.ts +1 -1
- package/dist/features/sketch-editor/dom.js +1 -1
- package/dist/features/sketch-editor/react/hooks/useSketchDomEnhancements.d.ts +1 -1
- package/dist/features/sketch-editor/react/hooks/useSketchDomEnhancements.js +11 -9
- package/dist/features/sketch-editor/types.d.ts +1 -1
- package/dist/features/sketch-editor/types.js +1 -1
- package/dist/features/skills/react/components/SkillDraftForm.js +1 -1
- package/dist/features/skills/react/components/SkillRow.js +1 -1
- package/dist/features/skills/react/components/SkillsTab.js +2 -2
- package/dist/features/source-config-list/agent-handles.js +2 -2
- package/dist/features/source-config-list/react/hooks/useSourceConfigAddForm.d.ts +1 -1
- package/dist/features/source-config-list/react/hooks/useSourceConfigAddForm.js +1 -1
- package/dist/features/source-config-list/rules.d.ts +1 -1
- package/dist/features/source-config-list/rules.js +3 -3
- package/dist/features/source-config-list/types.d.ts +1 -1
- package/dist/features/source-config-list/types.js +1 -1
- package/dist/features/tab-launcher-menu/react/hooks/useTabLauncherMenu.js +1 -1
- package/dist/features/tab-strip/index.d.ts +4 -0
- package/dist/features/tab-strip/index.js +2 -0
- package/dist/features/tab-strip/react/components/TabBar.d.ts +23 -0
- package/dist/features/tab-strip/react/components/TabBar.hooks.d.ts +25 -0
- package/dist/features/tab-strip/react/components/TabBar.hooks.js +57 -0
- package/dist/features/tab-strip/react/components/TabBar.js +18 -0
- package/dist/features/tab-strip/react/components/TabStrip.js +2 -1
- package/dist/features/tab-strip/react/components/TabStripFrame.d.ts +3 -0
- package/dist/features/tab-strip/react/components/TabStripFrame.js +7 -0
- package/dist/features/tabbed-dialog/react/hooks/useTabbedDialog.js +1 -1
- package/dist/features/tabbed-dialog/styles/tabbed-dialog.css +8 -141
- package/dist/features/tabbed-dialog/types.d.ts +1 -1
- package/dist/features/tabbed-dialog/types.js +1 -1
- package/dist/features/version-manager/ports.d.ts +1 -1
- package/dist/features/version-manager/react/components/VersionPromptPopover.js +5 -3
- package/dist/features/version-manager/react/components/VersionRestoreControl.js +1 -1
- package/dist/features/version-manager/types.d.ts +1 -1
- package/dist/features/version-manager/types.js +1 -1
- package/dist/features/viewer-shell/dependencies.js +1 -1
- package/dist/features/viewer-shell/ports.d.ts +1 -1
- package/dist/features/viewer-shell/react/components/ViewportSwitcher.js +1 -1
- package/dist/features/viewer-shell/react/hooks/useMarkdownScrollSync.d.ts +1 -1
- package/dist/features/viewer-shell/react/hooks/useMarkdownScrollSync.js +1 -1
- package/dist/features/viewer-shell/types.d.ts +1 -1
- package/dist/features/viewer-shell/types.js +1 -1
- package/dist/index.d.ts +420 -0
- package/dist/index.js +421 -1
- package/dist/react/components/ComponentKitPreview.js +1 -1
- package/dist/react/components/EditorIcon.d.ts +1 -1
- package/dist/react/components/EditorIcon.js +22 -22
- package/dist/react/components/ExportDiagnosticsButton.d.ts +8 -1
- package/dist/react/components/ExportDiagnosticsButton.js +6 -6
- package/dist/react/components/PaletteTweaks.js +5 -5
- package/dist/react/hooks/useBrandFonts.d.ts +19 -4
- package/dist/react/hooks/useBrandFonts.js +11 -7
- package/dist/react/hooks/useDebouncedValue.d.ts +12 -1
- package/dist/react/hooks/useDebouncedValue.js +9 -4
- package/dist/react/hooks/useStableHandler.d.ts +3 -1
- package/dist/react/hooks/useStableHandler.js +1 -1
- package/dist/renderers/annotation-canvas/drawing.d.ts +4 -4
- package/dist/renderers/annotation-canvas/drawing.js +28 -15
- package/dist/renderers/annotation-canvas/react/components/AnnotationCanvas.js +2 -2
- package/dist/renderers/annotation-canvas/react/hooks/useAnnotationCanvas.js +4 -3
- package/dist/renderers/annotation-canvas/rules.d.ts +1 -1
- package/dist/renderers/annotation-canvas/theme.d.ts +13 -0
- package/dist/renderers/annotation-canvas/theme.js +16 -0
- package/dist/renderers/annotation-canvas/types.d.ts +1 -1
- package/dist/renderers/index.d.ts +1 -1
- package/dist/renderers/new-tab-preview.d.ts +3 -0
- package/dist/renderers/new-tab-preview.js +3 -1
- package/dist/renderers/preview-modal-shell/react/hooks/usePreviewModalShell.d.ts +1 -1
- package/dist/renderers/preview-modal-shell/react/hooks/usePreviewModalShell.js +1 -1
- package/dist/renderers/preview-modal-shell/rules.d.ts +1 -1
- package/dist/renderers/preview-modal-shell/types.d.ts +3 -3
- package/dist/renderers/preview-modal-shell/types.js +2 -2
- package/dist/renderers/react/components/ArtifactView.d.ts +1 -1
- package/dist/renderers/react/components/ArtifactView.js +1 -1
- package/dist/renderers/registry.d.ts +12 -6
- package/dist/renderers/registry.js +7 -9
- package/dist/renderers/renderers/html.js +2 -2
- package/dist/renderers/renderers/index.js +1 -1
- package/dist/renderers/renderers/markdown.js +2 -2
- package/dist/renderers/renderers/react-component.js +1 -1
- package/dist/renderers/renderers/svg.js +1 -1
- package/dist/renderers/shiki.js +1 -1
- package/dist/renderers/srcdoc/build.d.ts +1 -1
- package/dist/renderers/srcdoc/build.js +1 -1
- package/dist/renderers/url-load-decision.d.ts +1 -1
- package/dist/renderers/url-load-decision.js +1 -1
- package/dist/styles/admin.css +273 -0
- package/dist/styles/variables.css +117 -0
- package/dist/theme/apply.d.ts +9 -0
- package/dist/theme/apply.js +77 -0
- package/dist/theme/color-scheme.d.ts +8 -0
- package/dist/theme/color-scheme.js +9 -0
- package/dist/theme/default.d.ts +4 -0
- package/dist/theme/default.js +11 -0
- package/dist/theme/index.d.ts +6 -0
- package/dist/theme/index.js +5 -0
- package/dist/theme/stylesheet.d.ts +8 -0
- package/dist/theme/stylesheet.js +14 -0
- package/dist/theme/types.d.ts +39 -0
- package/dist/theme/types.js +2 -0
- package/dist/theme/validation.d.ts +15 -0
- package/dist/theme/validation.js +55 -0
- package/dist/utils/appearance.js +1 -1
- package/dist/utils/browser-request.d.ts +12 -0
- package/dist/utils/browser-request.js +10 -0
- package/dist/utils/color-math.d.ts +20 -6
- package/dist/utils/color-math.js +12 -12
- package/dist/utils/copy-to-clipboard.d.ts +19 -8
- package/dist/utils/copy-to-clipboard.js +15 -11
- package/dist/utils/dom-subscriptions.js +1 -1
- package/dist/utils/endpoint-policy.d.ts +9 -3
- package/dist/utils/endpoint-policy.js +5 -5
- package/dist/utils/markdown-scroll-sync.js +3 -3
- package/dist/utils/notifications.d.ts +1 -1
- package/dist/utils/notifications.js +1 -1
- package/dist/utils/polygon-selection.d.ts +1 -1
- package/dist/utils/polygon-selection.js +1 -1
- package/dist/utils/scroll-tabs-with-wheel.d.ts +4 -1
- package/dist/utils/scroll-tabs-with-wheel.js +1 -1
- package/dist/utils/smooth-scroll-to-top.js +1 -1
- package/dist/utils/sse.d.ts +3 -1
- package/dist/utils/sse.js +1 -1
- package/package.json +105 -16
- package/dist/features/lexical-rich-text-editor/react/test-support/lexical-harness.d.ts +0 -11
- package/dist/features/lexical-rich-text-editor/react/test-support/lexical-harness.js +0 -35
|
@@ -28,10 +28,10 @@ import { testStatusLabel } from '../../rules.js';
|
|
|
28
28
|
export function NotificationsTab({ preferences, onChange, testNotificationTitle, testNotificationBody, labels, agentHandle, }) {
|
|
29
29
|
const t = useT();
|
|
30
30
|
const successSoundHandles = agentHandle
|
|
31
|
-
? buildAgentListHandles(agentSubHandle(agentHandle, 'success-sound'), SUCCESS_SOUNDS.map((s) => s.id))
|
|
31
|
+
? buildAgentListHandles({ prefix: agentSubHandle({ base: agentHandle, action: 'success-sound' }), ids: SUCCESS_SOUNDS.map((s) => s.id) })
|
|
32
32
|
: undefined;
|
|
33
33
|
const failureSoundHandles = agentHandle
|
|
34
|
-
? buildAgentListHandles(agentSubHandle(agentHandle, 'failure-sound'), FAILURE_SOUNDS.map((s) => s.id))
|
|
34
|
+
? buildAgentListHandles({ prefix: agentSubHandle({ base: agentHandle, action: 'failure-sound' }), ids: FAILURE_SOUNDS.map((s) => s.id) })
|
|
35
35
|
: undefined;
|
|
36
36
|
const [permission, setPermission] = useState(() => notificationPermission());
|
|
37
37
|
const [testStatus, setTestStatus] = useState(null);
|
|
@@ -76,16 +76,16 @@ export function NotificationsTab({ preferences, onChange, testNotificationTitle,
|
|
|
76
76
|
// outcome here rather than getting their own dead states.
|
|
77
77
|
setTestStatus(result === 'shown' ? 'sent' : 'failed');
|
|
78
78
|
};
|
|
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("div", { className: "jini-seg-control", role: "group", "aria-label": completionSoundTitle, style: { '--seg-cols': 1 }, children: _jsx("button", { type: "button", className: 'jini-seg-btn' + (preferences.soundEnabled ? ' active' : ''), "aria-pressed": preferences.soundEnabled, "aria-label": completionSoundTitle, onClick: toggleSound, ...agentHandleProps(agentHandle, { action: 'sound-toggle', role: 'button', label: completionSoundTitle }), children: _jsx("span", { className: "jini-seg-title", children: preferences.soundEnabled ? activeLabel : offLabel }) }) })] }), _jsx("p", { className: "jini-hint", children: completionSoundHint })] }), preferences.soundEnabled ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "jini-settings-field", children: [_jsx("label", { children: successSoundLabel }), _jsx("div", { className: "jini-seg-control", role: "group", "aria-label": successSoundLabel, style: { '--seg-cols': SUCCESS_SOUNDS.length }, children: SUCCESS_SOUNDS.map((sound, index) => (_jsx("button", { type: "button", className: 'jini-seg-btn' + (preferences.successSoundId === sound.id ? ' active' : ''), "aria-pressed": preferences.successSoundId === sound.id, onClick: () => {
|
|
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("div", { className: "jini-seg-control", role: "group", "aria-label": completionSoundTitle, style: { '--seg-cols': 1 }, children: _jsx("button", { type: "button", className: 'jini-seg-btn' + (preferences.soundEnabled ? ' active' : ''), "aria-pressed": preferences.soundEnabled, "aria-label": completionSoundTitle, onClick: toggleSound, ...agentHandleProps({}, { base: agentHandle, ...({ action: 'sound-toggle', role: 'button', label: completionSoundTitle }) }), children: _jsx("span", { className: "jini-seg-title", children: preferences.soundEnabled ? activeLabel : offLabel }) }) })] }), _jsx("p", { className: "jini-hint", children: completionSoundHint })] }), preferences.soundEnabled ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "jini-settings-field", children: [_jsx("label", { children: successSoundLabel }), _jsx("div", { className: "jini-seg-control", role: "group", "aria-label": successSoundLabel, style: { '--seg-cols': SUCCESS_SOUNDS.length }, children: SUCCESS_SOUNDS.map((sound, index) => (_jsx("button", { type: "button", className: 'jini-seg-btn' + (preferences.successSoundId === sound.id ? ' active' : ''), "aria-pressed": preferences.successSoundId === sound.id, onClick: () => {
|
|
80
80
|
onChange({ successSoundId: sound.id });
|
|
81
81
|
playSound(sound.id);
|
|
82
|
-
}, ...agentHandleProps(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: () => {
|
|
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
83
|
onChange({ failureSoundId: sound.id });
|
|
84
84
|
playSound(sound.id);
|
|
85
|
-
}, ...agentHandleProps(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: () => {
|
|
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
86
|
void toggleDesktop();
|
|
87
|
-
}, ...agentHandleProps(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: () => {
|
|
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: () => {
|
|
88
88
|
void sendTestNotification();
|
|
89
|
-
}, ...agentHandleProps(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] })] }));
|
|
89
|
+
}, ...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
90
|
}
|
|
91
91
|
//# sourceMappingURL=NotificationsTab.js.map
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Origin: `NotificationsSection` in `SettingsDialog.tsx` (completion-sound
|
|
3
3
|
* toggle/picker + browser Notification-permission flow), GENERIC per
|
|
4
4
|
* `ADS-memory/reports/jini-port/recon/r6-god-component-internals.md` §1.3 — browser-API
|
|
5
|
-
* only, zero product-domain coupling. See `packages/ui/
|
|
5
|
+
* only, zero product-domain coupling. See `packages/ui/archived provenance ledger` for
|
|
6
6
|
* the full provenance note.
|
|
7
7
|
*/
|
|
8
8
|
import type { SoundId } from './notifications-catalog.js';
|
|
@@ -59,7 +59,7 @@ export function installBootTimingObserver(options = {}) {
|
|
|
59
59
|
// only ever runs after that function's own `typeof window === 'undefined'`
|
|
60
60
|
// guard has already passed, so a second check here was dead code for every
|
|
61
61
|
// real call — removed rather than tested around (see
|
|
62
|
-
// packages/ui/
|
|
62
|
+
// packages/ui/archived provenance ledger's 2026-07-22 dated entry).
|
|
63
63
|
function schedule(fn) {
|
|
64
64
|
const rIC = window.requestIdleCallback;
|
|
65
65
|
if (typeof rIC === 'function') {
|
|
@@ -90,7 +90,7 @@ function emit(reporter) {
|
|
|
90
90
|
// resolvable in global scope by the time this line runs; a `typeof
|
|
91
91
|
// document !== 'undefined'` re-check here was dead code for every real
|
|
92
92
|
// call, removed rather than tested around (see
|
|
93
|
-
// packages/ui/
|
|
93
|
+
// packages/ui/archived provenance ledger's 2026-07-22 dated entry).
|
|
94
94
|
visibility_state: document.visibilityState,
|
|
95
95
|
});
|
|
96
96
|
}
|
|
@@ -90,7 +90,7 @@ function emitStuck(runId) {
|
|
|
90
90
|
// time this callback runs, its entry is guaranteed to both exist and
|
|
91
91
|
// have `emitted === false`. Left in place (not stripped) as a guard
|
|
92
92
|
// against a future refactor silently breaking that invariant — see
|
|
93
|
-
// packages/ui/
|
|
93
|
+
// packages/ui/archived provenance ledger's 2026-07-22 dated entry for the full proof,
|
|
94
94
|
// matching this task's own standard for documenting provable
|
|
95
95
|
// unreachability instead of forcing a test.
|
|
96
96
|
if (!entry || entry.emitted)
|
|
@@ -69,7 +69,7 @@ export function installWhiteScreenDetector(options = {}) {
|
|
|
69
69
|
// its callback afterward. So by the time either callback runs at all,
|
|
70
70
|
// `cancelled` is guaranteed to still be its initial `false`. Left in
|
|
71
71
|
// place (not stripped) as a guard against a future refactor silently
|
|
72
|
-
// breaking that invariant — see packages/ui/
|
|
72
|
+
// breaking that invariant — see packages/ui/archived provenance ledger's 2026-07-22
|
|
73
73
|
// dated entry for the full proof, same pattern as this package's
|
|
74
74
|
// `stuck-run.ts` `emitStuck` precedent.
|
|
75
75
|
const timer = window.setTimeout(() => {
|
|
@@ -105,7 +105,7 @@ function checkAppMounted(options) {
|
|
|
105
105
|
// `installWhiteScreenDetector`, itself only ever reachable after that
|
|
106
106
|
// function's own `typeof document === 'undefined'` guard has already
|
|
107
107
|
// passed — a second check here was dead code for every real call,
|
|
108
|
-
// removed rather than tested around (see packages/ui/
|
|
108
|
+
// removed rather than tested around (see packages/ui/archived provenance ledger's
|
|
109
109
|
// 2026-07-22 dated entry).
|
|
110
110
|
// Primary signal: the host's root-mount effect ran.
|
|
111
111
|
if (document.documentElement.getAttribute(options.mountedAttribute) === options.mountedAttributeValue) {
|
|
@@ -126,7 +126,7 @@ function checkAppMounted(options) {
|
|
|
126
126
|
// only for `Document`/`DocumentType` nodes — for any `Element` it is
|
|
127
127
|
// always a string (possibly `''`). The `?? ''` fallback this used to
|
|
128
128
|
// have was dead code for every real entry here, removed rather than
|
|
129
|
-
// tested around (see packages/ui/
|
|
129
|
+
// tested around (see packages/ui/archived provenance ledger's 2026-07-22 dated
|
|
130
130
|
// entry).
|
|
131
131
|
.map((el) => el.innerText ?? el.textContent)
|
|
132
132
|
.join('')
|
|
@@ -154,7 +154,7 @@ function monitorMount(isAppMounted, onMounted) {
|
|
|
154
154
|
// about): two synchronous body mutations followed by two microtask
|
|
155
155
|
// flushes produced exactly one callback invocation with `stopped` still
|
|
156
156
|
// `false` at its start, and a further post-disconnect mutation produced
|
|
157
|
-
// zero more. See packages/ui/
|
|
157
|
+
// zero more. See packages/ui/archived provenance ledger's 2026-07-22 dated entry for
|
|
158
158
|
// the full record.
|
|
159
159
|
const observer = new MutationObserver(() => {
|
|
160
160
|
if (stopped)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type FetchQueryEnvironmentPort } from './cache.js';
|
|
3
|
+
import type { CachedLoader, CachedLoaderOptions, FetchMutationOptions, FetchQueryOptions, MutationResult, QueryKey, QueryResult } from './types.js';
|
|
4
|
+
/** Isolated cache scope; no external provider or query-library singleton is needed. */
|
|
5
|
+
export declare function FetchQueryProvider({ children, environment }: {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
environment?: FetchQueryEnvironmentPort;
|
|
8
|
+
}): import("react").JSX.Element;
|
|
9
|
+
/**
|
|
10
|
+
* Without prior successful data, a disabled read hides cached errors so gesture-gated
|
|
11
|
+
* content cannot display a failure the user has not asked to retry. With prior data, retain
|
|
12
|
+
* a later background failure so callers can distinguish usable data from guaranteed freshness.
|
|
13
|
+
*/
|
|
14
|
+
/** Disabled reads keep earlier data but suppress errors before any successful read. */
|
|
15
|
+
export declare function resolveFetchQueryStatus({ disabled, hasData, status }: {
|
|
16
|
+
disabled: boolean;
|
|
17
|
+
hasData: boolean;
|
|
18
|
+
status: 'pending' | 'error' | 'success';
|
|
19
|
+
}): QueryResult<unknown>['status'];
|
|
20
|
+
export declare function resolveFetchQueryError({ error, disabled, hasData }: {
|
|
21
|
+
error: unknown;
|
|
22
|
+
disabled: boolean;
|
|
23
|
+
hasData: boolean;
|
|
24
|
+
}): Error | null;
|
|
25
|
+
/** Reactive keyed read. Background refresh keeps cached data visible. */
|
|
26
|
+
export declare function useFetchQuery<T>(required: Pick<FetchQueryOptions<T>, "key" | "fetch">, optional?: Omit<FetchQueryOptions<T>, "key" | "fetch">): QueryResult<T>;
|
|
27
|
+
/** A write returns its rejection to awaiting callers and also handles discarded promises. */
|
|
28
|
+
export declare function useFetchMutation<TInput, TOutput>(required: Pick<FetchMutationOptions<TInput, TOutput>, "run">, optional?: Omit<FetchMutationOptions<TInput, TOutput>, "run">): MutationResult<TInput, TOutput>;
|
|
29
|
+
/** Promise loader sharing the provider cache; replace wins over an older in-flight read. */
|
|
30
|
+
export declare function useCachedLoader<T>({ key, fetch }: Pick<CachedLoaderOptions<T>, "key" | "fetch">, { staleTime }?: Omit<CachedLoaderOptions<T>, "key" | "fetch">): CachedLoader<T>;
|
|
31
|
+
/** Prefix invalidation refreshes every currently enabled read under that key. */
|
|
32
|
+
export declare function useInvalidate(): (required: {
|
|
33
|
+
key: QueryKey;
|
|
34
|
+
}) => void;
|
|
35
|
+
//# sourceMappingURL=adapter.react.d.ts.map
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
|
|
3
|
+
import { DEFAULT_STALE_TIME, FetchQueryCache, toQueryError } from './cache.js';
|
|
4
|
+
import { browserFetchQueryEnvironment } from './environment.browser.js';
|
|
5
|
+
const CacheContext = createContext(null);
|
|
6
|
+
/** Isolated cache scope; no external provider or query-library singleton is needed. */
|
|
7
|
+
export function FetchQueryProvider({ children, environment = browserFetchQueryEnvironment }) {
|
|
8
|
+
// A module singleton would leak cache values between independent providers/tests,
|
|
9
|
+
// producing cases that pass alone but fail together. Scope ownership to this provider.
|
|
10
|
+
const cache = useMemo(() => new FetchQueryCache({}, { environment }), [environment]);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
cache.activate();
|
|
13
|
+
return () => cache.dispose();
|
|
14
|
+
}, [cache]);
|
|
15
|
+
return _jsx(CacheContext.Provider, { value: cache, children: children });
|
|
16
|
+
}
|
|
17
|
+
function useCache() {
|
|
18
|
+
const cache = useContext(CacheContext);
|
|
19
|
+
if (!cache)
|
|
20
|
+
throw new Error('Fetch-query hooks require FetchQueryProvider');
|
|
21
|
+
return cache;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Without prior successful data, a disabled read hides cached errors so gesture-gated
|
|
25
|
+
* content cannot display a failure the user has not asked to retry. With prior data, retain
|
|
26
|
+
* a later background failure so callers can distinguish usable data from guaranteed freshness.
|
|
27
|
+
*/
|
|
28
|
+
// Keep status/error folds outside subscription wiring so their branches are directly
|
|
29
|
+
// testable without mounting observers; flat decisions avoid nested conditional review costs.
|
|
30
|
+
/** Disabled reads keep earlier data but suppress errors before any successful read. */
|
|
31
|
+
export function resolveFetchQueryStatus({ disabled, hasData, status }) {
|
|
32
|
+
if (disabled)
|
|
33
|
+
return hasData ? 'success' : 'loading';
|
|
34
|
+
return status === 'pending' ? 'loading' : status;
|
|
35
|
+
}
|
|
36
|
+
export function resolveFetchQueryError({ error, disabled, hasData }) {
|
|
37
|
+
if (!error || (disabled && !hasData))
|
|
38
|
+
return null;
|
|
39
|
+
return toQueryError(error);
|
|
40
|
+
}
|
|
41
|
+
/** Reactive keyed read. Background refresh keeps cached data visible. */
|
|
42
|
+
export function useFetchQuery(required, optional = {}) {
|
|
43
|
+
const cache = useCache();
|
|
44
|
+
const options = { ...optional, ...required };
|
|
45
|
+
const { key, enabled = true, staleTime = DEFAULT_STALE_TIME, refetchOnWindowFocus = false } = options;
|
|
46
|
+
const latest = useRef(options);
|
|
47
|
+
latest.current = options;
|
|
48
|
+
const entry = cache.entry({ key: key }, { staleTime: staleTime });
|
|
49
|
+
const subscribe = useCallback((listener) => cache.subscribe({ entry: entry, listener: listener }), [cache, entry]);
|
|
50
|
+
const snapshot = useCallback(() => entry.state, [entry]);
|
|
51
|
+
const state = useSyncExternalStore(subscribe, snapshot, snapshot);
|
|
52
|
+
const fetch = useCallback(() => latest.current.fetch(), []);
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (!enabled)
|
|
55
|
+
return;
|
|
56
|
+
const release = cache.observe({ entry: entry, observer: { fetch, staleTime, focus: refetchOnWindowFocus } });
|
|
57
|
+
void cache.load({ entry: entry, fetch: fetch, staleTime: staleTime });
|
|
58
|
+
return release;
|
|
59
|
+
}, [cache, entry, enabled, staleTime, refetchOnWindowFocus, fetch]);
|
|
60
|
+
const refetch = useCallback(() => { void cache.load({ entry: entry, fetch: fetch, staleTime: staleTime }, { force: true }); }, [cache, entry, fetch, staleTime]);
|
|
61
|
+
const hasData = state.data !== undefined;
|
|
62
|
+
return {
|
|
63
|
+
data: state.data,
|
|
64
|
+
status: resolveFetchQueryStatus({ disabled: !enabled, hasData: hasData, status: state.status }),
|
|
65
|
+
error: resolveFetchQueryError({ error: state.error, disabled: !enabled, hasData: hasData }),
|
|
66
|
+
isFetching: state.isFetching, refetch,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/** A write returns its rejection to awaiting callers and also handles discarded promises. */
|
|
70
|
+
export function useFetchMutation(required, optional = {}) {
|
|
71
|
+
const cache = useCache();
|
|
72
|
+
const options = { ...optional, ...required };
|
|
73
|
+
const latest = useRef(options);
|
|
74
|
+
latest.current = options;
|
|
75
|
+
const generation = useRef(0);
|
|
76
|
+
const mounted = useRef(true);
|
|
77
|
+
useEffect(() => { mounted.current = true; return () => { mounted.current = false; }; }, []);
|
|
78
|
+
const [state, setState] = useState({ status: 'idle', error: null });
|
|
79
|
+
const mutate = useCallback(({ input }) => {
|
|
80
|
+
const current = ++generation.current;
|
|
81
|
+
const { run, invalidates } = latest.current;
|
|
82
|
+
setState({ status: 'pending', error: null });
|
|
83
|
+
// Surface a rejected write immediately instead of silently retrying authorization or
|
|
84
|
+
// validation failures that another identical attempt cannot fix.
|
|
85
|
+
const promise = cache.runOnline({ run: () => run({ input }) }).then(value => {
|
|
86
|
+
// Do not await dependent reads: the mutation promise means the write succeeded,
|
|
87
|
+
// not that every list finished reloading. Refetches proceed on their own schedule.
|
|
88
|
+
for (const key of invalidates ?? [])
|
|
89
|
+
cache.invalidate({ prefix: key });
|
|
90
|
+
if (mounted.current && current === generation.current)
|
|
91
|
+
setState({ status: 'success', error: null });
|
|
92
|
+
return value;
|
|
93
|
+
}, error => {
|
|
94
|
+
if (mounted.current && current === generation.current)
|
|
95
|
+
setState({ status: 'error', error: toQueryError(error) });
|
|
96
|
+
throw error;
|
|
97
|
+
});
|
|
98
|
+
// Handle this same promise for callers that discard it, while still returning it so
|
|
99
|
+
// await/catch observes the original rejection; discarding the derived promise is intentional.
|
|
100
|
+
void promise.catch(() => { });
|
|
101
|
+
return promise;
|
|
102
|
+
}, [cache]);
|
|
103
|
+
const reset = useCallback(() => { generation.current += 1; setState({ status: 'idle', error: null }); }, []);
|
|
104
|
+
return { ...state, mutate, reset };
|
|
105
|
+
}
|
|
106
|
+
/** Promise loader sharing the provider cache; replace wins over an older in-flight read. */
|
|
107
|
+
export function useCachedLoader({ key, fetch }, { staleTime = DEFAULT_STALE_TIME } = {}) {
|
|
108
|
+
const cache = useCache();
|
|
109
|
+
return useMemo(() => ({
|
|
110
|
+
peek: () => cache.entry({ key: key }, { staleTime: staleTime }).state.data,
|
|
111
|
+
load: () => cache.load({ entry: cache.entry({ key: key }, { staleTime: staleTime }), fetch: fetch, staleTime: staleTime }),
|
|
112
|
+
replace: ({ value }) => cache.replace({ entry: cache.entry({ key: key }, { staleTime: staleTime }), data: value }),
|
|
113
|
+
}), [cache, key, fetch, staleTime]);
|
|
114
|
+
}
|
|
115
|
+
// Imperative invalidation also covers externally delivered events, such as an SSE change
|
|
116
|
+
// feed, whose producer cannot call a React read hook or a component-local reload.
|
|
117
|
+
/** Prefix invalidation refreshes every currently enabled read under that key. */
|
|
118
|
+
export function useInvalidate() {
|
|
119
|
+
const cache = useCache();
|
|
120
|
+
return useCallback(({ key }) => cache.invalidate({ prefix: key }), [cache]);
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=adapter.react.js.map
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { Clock } from '@jini-ai/core/primitives';
|
|
2
|
+
import type { QueryKey } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Ten seconds covers reading, closing and reopening a record or bouncing between screens
|
|
5
|
+
* without paying for another identical request. Five seconds was too short for that read
|
|
6
|
+
* cycle; thirty seconds exposed more other-operator staleness than the conservative initial
|
|
7
|
+
* shared default should allow. There is no default polling/focus refresh, so this bounds
|
|
8
|
+
* the extra exposure on a quick revisit. Write invalidation bypasses freshness; reads that
|
|
9
|
+
* must be fresh on every mount can choose staleTime: 0 without lowering every query default.
|
|
10
|
+
*/
|
|
11
|
+
export declare const DEFAULT_STALE_TIME = 10000;
|
|
12
|
+
export interface QueryCacheSchedulerPort {
|
|
13
|
+
schedule(required: {
|
|
14
|
+
callback: () => void;
|
|
15
|
+
delayMs: number;
|
|
16
|
+
}): unknown;
|
|
17
|
+
cancel(required: {
|
|
18
|
+
handle: unknown;
|
|
19
|
+
}): void;
|
|
20
|
+
}
|
|
21
|
+
/** Host connectivity and focus signals; the cache itself has no DOM dependency. */
|
|
22
|
+
export interface FetchQueryEnvironmentPort {
|
|
23
|
+
isOnline(): boolean;
|
|
24
|
+
subscribeOnline(required: {
|
|
25
|
+
listener: (online: boolean) => void;
|
|
26
|
+
}): () => void;
|
|
27
|
+
subscribeFocus(required: {
|
|
28
|
+
listener: () => void;
|
|
29
|
+
}): () => void;
|
|
30
|
+
}
|
|
31
|
+
export interface FetchQueryCacheOptions {
|
|
32
|
+
clock?: Clock;
|
|
33
|
+
scheduler?: QueryCacheSchedulerPort;
|
|
34
|
+
environment?: FetchQueryEnvironmentPort;
|
|
35
|
+
}
|
|
36
|
+
export interface QueryState {
|
|
37
|
+
data: unknown;
|
|
38
|
+
error: Error | null;
|
|
39
|
+
status: 'pending' | 'success' | 'error';
|
|
40
|
+
isFetching: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface QueryObserver {
|
|
43
|
+
fetch: () => Promise<unknown>;
|
|
44
|
+
staleTime: number;
|
|
45
|
+
focus: boolean;
|
|
46
|
+
}
|
|
47
|
+
export interface CacheEntry {
|
|
48
|
+
key: QueryKey;
|
|
49
|
+
state: QueryState;
|
|
50
|
+
updatedAt: number;
|
|
51
|
+
invalidated: boolean;
|
|
52
|
+
replacement: number;
|
|
53
|
+
pending: Promise<unknown> | undefined;
|
|
54
|
+
refreshAfterPending: boolean;
|
|
55
|
+
listeners: Set<() => void>;
|
|
56
|
+
observers: Set<QueryObserver>;
|
|
57
|
+
retention: number;
|
|
58
|
+
eviction: unknown;
|
|
59
|
+
}
|
|
60
|
+
export declare function toQueryError(value: unknown): Error;
|
|
61
|
+
/** One provider-owned cache. Requests are shared by structural primitive keys.
|
|
62
|
+
* Failed reads retain earlier data; replacement writes supersede older requests.
|
|
63
|
+
* Idle entries live for at least five minutes or their declared freshness period. */
|
|
64
|
+
export declare class FetchQueryCache {
|
|
65
|
+
private readonly entries;
|
|
66
|
+
private disposed;
|
|
67
|
+
private readonly clock;
|
|
68
|
+
private readonly scheduler;
|
|
69
|
+
private readonly environment;
|
|
70
|
+
private online;
|
|
71
|
+
private readonly onlineWaiters;
|
|
72
|
+
private releaseEnvironment;
|
|
73
|
+
constructor(_required?: Record<string, never>, { clock, scheduler, environment }?: FetchQueryCacheOptions);
|
|
74
|
+
entry({ key }: {
|
|
75
|
+
key: QueryKey;
|
|
76
|
+
}, { staleTime }?: {
|
|
77
|
+
staleTime?: number;
|
|
78
|
+
}): CacheEntry;
|
|
79
|
+
fresh({ entry, staleTime }: {
|
|
80
|
+
entry: CacheEntry;
|
|
81
|
+
staleTime: number;
|
|
82
|
+
}): boolean;
|
|
83
|
+
subscribe({ entry, listener }: {
|
|
84
|
+
entry: CacheEntry;
|
|
85
|
+
listener: () => void;
|
|
86
|
+
}): () => void;
|
|
87
|
+
observe({ entry, observer }: {
|
|
88
|
+
entry: CacheEntry;
|
|
89
|
+
observer: QueryObserver;
|
|
90
|
+
}): () => void;
|
|
91
|
+
/** Cache hits respect freshness; explicit refreshes restart background reads.
|
|
92
|
+
* Initial reads still deduplicate because no earlier successful data exists to refresh. */
|
|
93
|
+
load<T>({ entry, fetch, staleTime }: {
|
|
94
|
+
entry: CacheEntry;
|
|
95
|
+
fetch: () => Promise<T>;
|
|
96
|
+
staleTime: number;
|
|
97
|
+
}, { force }?: {
|
|
98
|
+
force?: boolean;
|
|
99
|
+
}): Promise<T>;
|
|
100
|
+
replace({ entry, data }: {
|
|
101
|
+
entry: CacheEntry;
|
|
102
|
+
data: unknown;
|
|
103
|
+
}): void;
|
|
104
|
+
invalidate({ prefix }: {
|
|
105
|
+
prefix: QueryKey;
|
|
106
|
+
}): void;
|
|
107
|
+
onFocus(): void;
|
|
108
|
+
/** Execute a read/write once connectivity returns; failures are not retried.
|
|
109
|
+
* @param required.run Operation to invoke online.
|
|
110
|
+
* @returns The operation's original result or rejection. */
|
|
111
|
+
runOnline<T>({ run }: {
|
|
112
|
+
run: () => Promise<T>;
|
|
113
|
+
}): Promise<T>;
|
|
114
|
+
/** Stop timers on provider disposal; reactivate for React's effect replay. */
|
|
115
|
+
activate(): void;
|
|
116
|
+
dispose(): void;
|
|
117
|
+
private onOnline;
|
|
118
|
+
private refresh;
|
|
119
|
+
private publish;
|
|
120
|
+
private cancelEviction;
|
|
121
|
+
private scheduleEviction;
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ten seconds covers reading, closing and reopening a record or bouncing between screens
|
|
3
|
+
* without paying for another identical request. Five seconds was too short for that read
|
|
4
|
+
* cycle; thirty seconds exposed more other-operator staleness than the conservative initial
|
|
5
|
+
* shared default should allow. There is no default polling/focus refresh, so this bounds
|
|
6
|
+
* the extra exposure on a quick revisit. Write invalidation bypasses freshness; reads that
|
|
7
|
+
* must be fresh on every mount can choose staleTime: 0 without lowering every query default.
|
|
8
|
+
*/
|
|
9
|
+
export const DEFAULT_STALE_TIME = 10_000;
|
|
10
|
+
const IDLE_RETENTION = 5 * 60_000;
|
|
11
|
+
const browserScheduler = {
|
|
12
|
+
schedule: ({ callback, delayMs }) => setTimeout(callback, delayMs),
|
|
13
|
+
cancel: ({ handle }) => clearTimeout(handle),
|
|
14
|
+
};
|
|
15
|
+
// Normalize unknown promise rejections once so consumers receive Error without repeating
|
|
16
|
+
// the same narrowing boilerplate at every read and write call site.
|
|
17
|
+
export function toQueryError(value) {
|
|
18
|
+
return value instanceof Error ? value : new Error(typeof value === 'string' && value.trim() ? value : 'request failed');
|
|
19
|
+
}
|
|
20
|
+
/** One provider-owned cache. Requests are shared by structural primitive keys.
|
|
21
|
+
* Failed reads retain earlier data; replacement writes supersede older requests.
|
|
22
|
+
* Idle entries live for at least five minutes or their declared freshness period. */
|
|
23
|
+
export class FetchQueryCache {
|
|
24
|
+
entries = new Map();
|
|
25
|
+
disposed = false;
|
|
26
|
+
clock;
|
|
27
|
+
scheduler;
|
|
28
|
+
environment;
|
|
29
|
+
online;
|
|
30
|
+
onlineWaiters = new Set();
|
|
31
|
+
releaseEnvironment;
|
|
32
|
+
constructor(_required = {}, { clock = { nowMs: () => Date.now() }, scheduler = browserScheduler, environment } = {}) {
|
|
33
|
+
this.clock = clock;
|
|
34
|
+
this.scheduler = scheduler;
|
|
35
|
+
this.environment = environment;
|
|
36
|
+
this.online = environment?.isOnline() ?? true;
|
|
37
|
+
}
|
|
38
|
+
entry({ key }, { staleTime = DEFAULT_STALE_TIME } = {}) {
|
|
39
|
+
const hash = JSON.stringify(key);
|
|
40
|
+
let entry = this.entries.get(hash);
|
|
41
|
+
if (!entry) {
|
|
42
|
+
entry = {
|
|
43
|
+
key: [...key], state: { data: undefined, error: null, status: 'pending', isFetching: false },
|
|
44
|
+
updatedAt: 0, invalidated: false, replacement: 0, pending: undefined,
|
|
45
|
+
refreshAfterPending: false, listeners: new Set(), observers: new Set(),
|
|
46
|
+
retention: IDLE_RETENTION, eviction: undefined,
|
|
47
|
+
};
|
|
48
|
+
this.entries.set(hash, entry);
|
|
49
|
+
}
|
|
50
|
+
// An imperative load has no standing observer: idle eviction must not drop a value
|
|
51
|
+
// earlier than its declared freshness and unexpectedly force another network request.
|
|
52
|
+
entry.retention = Math.max(entry.retention, staleTime);
|
|
53
|
+
return entry;
|
|
54
|
+
}
|
|
55
|
+
fresh({ entry, staleTime }) {
|
|
56
|
+
return entry.state.data !== undefined && !entry.invalidated && this.clock.nowMs() - entry.updatedAt < staleTime;
|
|
57
|
+
}
|
|
58
|
+
subscribe({ entry, listener }) {
|
|
59
|
+
this.cancelEviction(entry);
|
|
60
|
+
entry.listeners.add(listener);
|
|
61
|
+
return () => { entry.listeners.delete(listener); this.scheduleEviction(entry); };
|
|
62
|
+
}
|
|
63
|
+
observe({ entry, observer }) {
|
|
64
|
+
entry.observers.add(observer);
|
|
65
|
+
return () => { entry.observers.delete(observer); this.scheduleEviction(entry); };
|
|
66
|
+
}
|
|
67
|
+
/** Cache hits respect freshness; explicit refreshes restart background reads.
|
|
68
|
+
* Initial reads still deduplicate because no earlier successful data exists to refresh. */
|
|
69
|
+
load({ entry, fetch, staleTime }, { force = false } = {}) {
|
|
70
|
+
this.cancelEviction(entry);
|
|
71
|
+
if (entry.pending && (!force || entry.state.data === undefined))
|
|
72
|
+
return entry.pending;
|
|
73
|
+
if (!force && this.fresh({ entry: entry, staleTime: staleTime })) {
|
|
74
|
+
this.scheduleEviction(entry);
|
|
75
|
+
return Promise.resolve(entry.state.data);
|
|
76
|
+
}
|
|
77
|
+
// Shared per key across loader instances: a replacement while this request is out
|
|
78
|
+
// must win over its older answer, not just protect the instance that issued the write.
|
|
79
|
+
const replacement = entry.replacement;
|
|
80
|
+
const run = () => {
|
|
81
|
+
// A queued offline refresh may have been superseded before reconnection.
|
|
82
|
+
if (entry.pending !== pending)
|
|
83
|
+
return (entry.pending ?? Promise.resolve(entry.state.data));
|
|
84
|
+
this.publish(entry, { ...entry.state, isFetching: true });
|
|
85
|
+
return fetch();
|
|
86
|
+
};
|
|
87
|
+
const pending = Promise.resolve().then(() => {
|
|
88
|
+
if (this.online)
|
|
89
|
+
return run();
|
|
90
|
+
this.publish(entry, { ...entry.state, isFetching: false });
|
|
91
|
+
return this.runOnline({ run });
|
|
92
|
+
}).then(value => {
|
|
93
|
+
const data = replacement === entry.replacement ? value : entry.state.data;
|
|
94
|
+
// Undefined is never successful query data, including an initial or background read.
|
|
95
|
+
if (data === undefined)
|
|
96
|
+
throw new Error(`${JSON.stringify(entry.key)} data is undefined`);
|
|
97
|
+
return data;
|
|
98
|
+
}).then(data => {
|
|
99
|
+
// Only the current request may publish or clear invalidation. An older transport
|
|
100
|
+
// need not support abort: its callers follow the replacement request instead.
|
|
101
|
+
if (entry.pending !== pending)
|
|
102
|
+
return (entry.pending ?? entry.state.data);
|
|
103
|
+
entry.updatedAt = this.clock.nowMs();
|
|
104
|
+
entry.invalidated = false;
|
|
105
|
+
this.publish(entry, { data, error: null, status: 'success', isFetching: false });
|
|
106
|
+
return data;
|
|
107
|
+
}, error => {
|
|
108
|
+
if (entry.pending !== pending)
|
|
109
|
+
return (entry.pending ?? entry.state.data);
|
|
110
|
+
// A replacement write is newer than this read's failure as well as its data.
|
|
111
|
+
// Keep the write's timestamp and freshness; only settle the obsolete fetch flag.
|
|
112
|
+
if (replacement !== entry.replacement) {
|
|
113
|
+
this.publish(entry, { ...entry.state, isFetching: false });
|
|
114
|
+
return entry.state.data;
|
|
115
|
+
}
|
|
116
|
+
entry.invalidated = true;
|
|
117
|
+
this.publish(entry, { ...entry.state, error: toQueryError(error), status: 'error', isFetching: false });
|
|
118
|
+
throw error;
|
|
119
|
+
}).finally(() => {
|
|
120
|
+
if (entry.pending !== pending)
|
|
121
|
+
return;
|
|
122
|
+
entry.pending = undefined;
|
|
123
|
+
if (entry.refreshAfterPending) {
|
|
124
|
+
entry.refreshAfterPending = false;
|
|
125
|
+
this.refresh(entry);
|
|
126
|
+
}
|
|
127
|
+
this.scheduleEviction(entry);
|
|
128
|
+
});
|
|
129
|
+
entry.pending = pending;
|
|
130
|
+
// Mark handled even when a UI caller deliberately discards the promise.
|
|
131
|
+
void pending.catch(() => { });
|
|
132
|
+
// Like TanStack's background refetches, retain the last error until success clears it
|
|
133
|
+
// or another failure replaces it. Extend that rule to retries without cached data:
|
|
134
|
+
// starting a request is not evidence of recovery; isFetching reports its progress.
|
|
135
|
+
const state = entry.state.data === undefined ? { ...entry.state, status: 'pending' } : entry.state;
|
|
136
|
+
this.publish(entry, { ...state, isFetching: this.online });
|
|
137
|
+
return pending;
|
|
138
|
+
}
|
|
139
|
+
replace({ entry, data }) {
|
|
140
|
+
entry.replacement += 1;
|
|
141
|
+
entry.updatedAt = this.clock.nowMs();
|
|
142
|
+
entry.invalidated = false;
|
|
143
|
+
this.publish(entry, { data, error: null, status: 'success', isFetching: entry.state.isFetching });
|
|
144
|
+
this.scheduleEviction(entry);
|
|
145
|
+
}
|
|
146
|
+
invalidate({ prefix }) {
|
|
147
|
+
for (const entry of this.entries.values()) {
|
|
148
|
+
if (!prefix.every((part, index) => entry.key[index] === part))
|
|
149
|
+
continue;
|
|
150
|
+
entry.invalidated = true;
|
|
151
|
+
this.refresh(entry);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
onFocus() {
|
|
155
|
+
for (const entry of this.entries.values()) {
|
|
156
|
+
const observer = [...entry.observers].find(value => value.focus && !this.fresh({ entry: entry, staleTime: value.staleTime }));
|
|
157
|
+
// Environmental refreshes share a request already underway, unlike an explicit gesture.
|
|
158
|
+
if (observer && !entry.pending)
|
|
159
|
+
void this.load({ entry: entry, fetch: observer.fetch, staleTime: observer.staleTime });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/** Execute a read/write once connectivity returns; failures are not retried.
|
|
163
|
+
* @param required.run Operation to invoke online.
|
|
164
|
+
* @returns The operation's original result or rejection. */
|
|
165
|
+
runOnline({ run }) {
|
|
166
|
+
if (this.online)
|
|
167
|
+
return Promise.resolve().then(() => {
|
|
168
|
+
// Connectivity may change between queuing the operation and its invocation.
|
|
169
|
+
if (!this.online)
|
|
170
|
+
return this.runOnline({ run });
|
|
171
|
+
return run();
|
|
172
|
+
});
|
|
173
|
+
return new Promise(resolve => { this.onlineWaiters.add(resolve); }).then(() => this.runOnline({ run }));
|
|
174
|
+
}
|
|
175
|
+
/** Stop timers on provider disposal; reactivate for React's effect replay. */
|
|
176
|
+
activate() {
|
|
177
|
+
this.disposed = false;
|
|
178
|
+
if (!this.environment || this.releaseEnvironment)
|
|
179
|
+
return;
|
|
180
|
+
const releaseOnline = this.environment.subscribeOnline({ listener: online => this.onOnline({ online }) });
|
|
181
|
+
const releaseFocus = this.environment.subscribeFocus({ listener: () => this.onFocus() });
|
|
182
|
+
this.releaseEnvironment = () => { releaseOnline(); releaseFocus(); };
|
|
183
|
+
this.onOnline({ online: this.environment.isOnline() });
|
|
184
|
+
}
|
|
185
|
+
dispose() {
|
|
186
|
+
this.disposed = true;
|
|
187
|
+
this.releaseEnvironment?.();
|
|
188
|
+
this.releaseEnvironment = undefined;
|
|
189
|
+
for (const entry of this.entries.values())
|
|
190
|
+
this.cancelEviction(entry);
|
|
191
|
+
}
|
|
192
|
+
onOnline({ online }) {
|
|
193
|
+
const reconnect = online && !this.online;
|
|
194
|
+
this.online = online;
|
|
195
|
+
if (!online)
|
|
196
|
+
return;
|
|
197
|
+
for (const resume of this.onlineWaiters)
|
|
198
|
+
resume();
|
|
199
|
+
this.onlineWaiters.clear();
|
|
200
|
+
if (!reconnect)
|
|
201
|
+
return;
|
|
202
|
+
for (const entry of this.entries.values()) {
|
|
203
|
+
const observer = [...entry.observers].find(value => !this.fresh({ entry, staleTime: value.staleTime }));
|
|
204
|
+
// A paused request already owns its resumption; restarting it here would double-fetch.
|
|
205
|
+
if (observer && !entry.pending)
|
|
206
|
+
void this.load({ entry, fetch: observer.fetch, staleTime: observer.staleTime });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
refresh(entry) {
|
|
210
|
+
const observer = entry.observers.values().next().value;
|
|
211
|
+
if (observer)
|
|
212
|
+
void this.load({ entry: entry, fetch: observer.fetch, staleTime: observer.staleTime }, { force: true });
|
|
213
|
+
}
|
|
214
|
+
publish(entry, state) {
|
|
215
|
+
entry.state = state;
|
|
216
|
+
for (const listener of entry.listeners)
|
|
217
|
+
listener();
|
|
218
|
+
}
|
|
219
|
+
cancelEviction(entry) {
|
|
220
|
+
if (entry.eviction !== undefined)
|
|
221
|
+
this.scheduler.cancel({ handle: entry.eviction });
|
|
222
|
+
entry.eviction = undefined;
|
|
223
|
+
}
|
|
224
|
+
scheduleEviction(entry) {
|
|
225
|
+
this.cancelEviction(entry);
|
|
226
|
+
if (this.disposed || entry.listeners.size || entry.observers.size || entry.pending || !Number.isFinite(entry.retention))
|
|
227
|
+
return;
|
|
228
|
+
entry.eviction = this.scheduler.schedule({ callback: () => this.entries.delete(JSON.stringify(entry.key)), delayMs: entry.retention });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { FetchQueryEnvironmentPort } from './cache.js';
|
|
2
|
+
/** Browser-only adapter: subscriptions are owned by the provider effect, never module scope. */
|
|
3
|
+
export declare const browserFetchQueryEnvironment: FetchQueryEnvironmentPort;
|
|
4
|
+
//# sourceMappingURL=environment.browser.d.ts.map
|