@notis_ai/cli 0.2.18 → 0.2.19
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/README.md +542 -146
- package/bin/check-runtime.js +15 -0
- package/bin/notis.js +2 -0
- package/config/notis_app_design_rules.json +320 -0
- package/config/portal-route-roots.json +45 -0
- package/dist/agent-hooks/notis-agent-hook.mjs +42325 -18273
- package/dist/base-skills/notis-cli/.notis-base-skill.json +1 -0
- package/dist/base-skills/notis-cli/SKILL.md +51 -280
- package/dist/base-skills/notis-cli/references/app-delivery.md +17 -0
- package/dist/base-skills/notis-cli/references/intelligence.md +94 -0
- package/dist/base-skills/notis-cli/references/native-databases.md +21 -0
- package/dist/base-skills/notis-cli/references/tool-examples.md +56 -0
- package/dist/base-skills/notis-cli/references/troubleshooting.md +67 -0
- package/dist/base-skills/notis-query/.notis-base-skill.json +1 -0
- package/dist/base-skills/notis-query/SKILL.md +35 -699
- package/dist/base-skills/notis-query/references/database-discovery.md +59 -0
- package/dist/base-skills/notis-query/references/documents.md +74 -0
- package/dist/base-skills/notis-query/references/query.md +76 -0
- package/dist/base-skills/notis-spaces/.notis-base-skill.json +1 -0
- package/dist/base-skills/notis-spaces/SKILL.md +218 -0
- package/dist/base-skills/notis-spaces/references/architecture.md +116 -0
- package/dist/base-skills/notis-spaces/references/context.md +81 -0
- package/dist/base-skills/notis-spaces/references/design.md +141 -0
- package/dist/base-skills/notis-spaces/references/fixtures.md +29 -0
- package/dist/base-skills/notis-spaces/references/memory.md +58 -0
- package/dist/base-skills/notis-spaces/references/reading.md +73 -0
- package/dist/base-skills/notis-spaces/references/release.md +120 -0
- package/dist/base-skills/notis-spaces/references/reports.md +77 -0
- package/dist/base-skills/notis-spaces/references/sdk.md +273 -0
- package/dist/base-skills/notis-spaces/references/store.md +118 -0
- package/dist/base-skills/notis-spaces/references/troubleshooting.md +28 -0
- package/dist/base-skills/notis-spaces/references/views.md +229 -0
- package/{template/packages → dist}/sdk/package.json +9 -10
- package/dist/sdk/src/agentContext.ts +36 -0
- package/dist/sdk/src/bundleModule.ts +175 -0
- package/dist/sdk/src/components/Dialog.tsx +103 -0
- package/dist/sdk/src/components/DocumentEditor.tsx +113 -0
- package/dist/sdk/src/components/DocumentPage.tsx +184 -0
- package/{template/packages → dist}/sdk/src/components/Markdown.tsx +9 -3
- package/{template/packages → dist}/sdk/src/components/MarkdownEditor.tsx +26 -5
- package/{template/packages → dist}/sdk/src/components/MultiSelectActionBar.tsx +95 -12
- package/{template/packages → dist}/sdk/src/components/MultiSelectCheckbox.tsx +4 -2
- package/dist/sdk/src/components/NotisCommentBoundary.tsx +172 -0
- package/{template/packages → dist}/sdk/src/components/NotisSelectionBoundary.tsx +9 -5
- package/dist/sdk/src/components/RecordComponents.tsx +111 -0
- package/dist/sdk/src/components/Skeleton.tsx +38 -0
- package/dist/sdk/src/config.ts +31 -0
- package/{template/packages → dist}/sdk/src/documents.ts +21 -6
- package/dist/sdk/src/hooks/useAction.ts +28 -0
- package/dist/sdk/src/hooks/useAgentContext.ts +23 -0
- package/{template/packages → dist}/sdk/src/hooks/useBackend.ts +3 -2
- package/dist/sdk/src/hooks/useCloudComputer.ts +94 -0
- package/dist/sdk/src/hooks/useCloudComputerShell.ts +114 -0
- package/{template/packages → dist}/sdk/src/hooks/useCollectionInteractions.ts +157 -147
- package/dist/sdk/src/hooks/useDatabaseSchema.ts +50 -0
- package/{template/packages → dist}/sdk/src/hooks/useDatabaseSubscription.ts +2 -2
- package/dist/sdk/src/hooks/useDocument.ts +51 -0
- package/{template/packages → dist}/sdk/src/hooks/useDocuments.ts +25 -59
- package/{template/packages → dist}/sdk/src/hooks/useHandover.ts +7 -7
- package/dist/sdk/src/hooks/useLongPressSelection.ts +79 -0
- package/dist/sdk/src/hooks/useMarqueeSelection.ts +194 -0
- package/{template/packages → dist}/sdk/src/hooks/useMultiSelect.ts +2 -8
- package/dist/sdk/src/hooks/useNativeDocumentBody.ts +50 -0
- package/dist/sdk/src/hooks/useNativeDocuments.ts +143 -0
- package/{template/packages → dist}/sdk/src/hooks/useNotis.ts +6 -1
- package/dist/sdk/src/hooks/useNotisNavigation.ts +70 -0
- package/dist/sdk/src/hooks/useQuery.ts +81 -0
- package/dist/sdk/src/hooks/useToolQuery.ts +12 -0
- package/dist/sdk/src/hooks/useTopBarSearch.ts +102 -0
- package/dist/sdk/src/hooks/useTopBarTitleLeaf.ts +75 -0
- package/{template/packages → dist}/sdk/src/hooks/useUpsertDocument.ts +1 -1
- package/dist/sdk/src/hooks/useViewParams.ts +195 -0
- package/dist/sdk/src/hooks/useViewerReads.ts +129 -0
- package/{template/packages → dist}/sdk/src/index.ts +64 -1
- package/{template/packages → dist}/sdk/src/interactions/actions.ts +14 -1
- package/{template/packages → dist}/sdk/src/interactions/shortcuts.tsx +129 -23
- package/dist/sdk/src/interactions/visibility.ts +13 -0
- package/{template/packages → dist}/sdk/src/interactions.ts +5 -1
- package/dist/sdk/src/nativeData.ts +39 -0
- package/{template/packages → dist}/sdk/src/provider.tsx +9 -4
- package/dist/sdk/src/queryCache.ts +292 -0
- package/dist/sdk/src/queryErrors.ts +51 -0
- package/dist/sdk/src/runtime.ts +675 -0
- package/dist/sdk/src/runtimeModules.ts +33 -0
- package/dist/sdk/src/space.ts +442 -0
- package/dist/sdk/src/standardViewParams.ts +36 -0
- package/{template/packages → dist}/sdk/src/styles.css +81 -16
- package/dist/sdk/src/tailwind.ts +66 -0
- package/dist/sdk/src/ui/charts/RenderChartData.tsx +25 -0
- package/dist/sdk/src/ui/engine/core.ts +392 -0
- package/dist/sdk/src/ui/engine/dom.ts +183 -0
- package/dist/sdk/src/ui/engine/hooks.ts +195 -0
- package/dist/sdk/src/ui/engine/instance.ts +63 -0
- package/dist/sdk/src/ui/engine/keycapContext.ts +2 -0
- package/dist/sdk/src/ui/engine/registry.ts +262 -0
- package/dist/sdk/src/ui/engine/types.ts +62 -0
- package/dist/sdk/src/ui/icons.tsx +91 -0
- package/dist/sdk/src/ui/util.ts +83 -0
- package/dist/sdk/src/viewPeriod.ts +26 -0
- package/dist/sdk/src/vite.ts +92 -0
- package/dist/sdk/src/words/actions.ts +295 -0
- package/dist/sdk/src/words/clashes.ts +158 -0
- package/dist/sdk/src/words/format.ts +136 -0
- package/dist/sdk/src/words/index.ts +13 -0
- package/dist/sdk/src/words/keymap.ts +72 -0
- package/dist/sdk/src/words/keys.ts +297 -0
- package/dist/sdk/src/words/shell.ts +46 -0
- package/dist/sdk/src/words/types.ts +65 -0
- package/dist/skill-sync/index.js +832 -151
- package/dist/skill-sync/index.js.map +4 -4
- package/dist/skill-sync-worker.mjs +3941 -0
- package/dist/space-harness/host.js +1460 -0
- package/dist/space-sdk/dist/chunk-ZSN57ATH.js +5 -0
- package/dist/space-sdk/dist/index.js +15 -0
- package/dist/space-sdk/dist/ui.js +18 -0
- package/dist/space-sdk/package.json +33 -0
- package/dist/space-sdk/src/agentContext.d.ts +36 -0
- package/dist/space-sdk/src/components/Dialog.d.ts +20 -0
- package/dist/space-sdk/src/components/DocumentEditor.d.ts +46 -0
- package/dist/space-sdk/src/components/DocumentPage.d.ts +53 -0
- package/dist/space-sdk/src/components/Markdown.d.ts +18 -0
- package/dist/space-sdk/src/components/MarkdownEditor.d.ts +11 -0
- package/dist/space-sdk/src/components/MultiSelectActionBar.d.ts +31 -0
- package/dist/space-sdk/src/components/MultiSelectCheckbox.d.ts +14 -0
- package/dist/space-sdk/src/components/MultiSelectDragOverlay.d.ts +8 -0
- package/dist/space-sdk/src/components/NotisCommentBoundary.d.ts +36 -0
- package/dist/space-sdk/src/components/NotisSelectionBoundary.d.ts +16 -0
- package/dist/space-sdk/src/components/RecordComponents.d.ts +64 -0
- package/dist/space-sdk/src/components/ShortcutHints.d.ts +12 -0
- package/dist/space-sdk/src/components/Skeleton.d.ts +23 -0
- package/dist/space-sdk/src/config.ts +31 -0
- package/dist/space-sdk/src/documents.d.ts +59 -0
- package/dist/space-sdk/src/hooks/useAction.d.ts +8 -0
- package/dist/space-sdk/src/hooks/useActiveResource.d.ts +3 -0
- package/dist/space-sdk/src/hooks/useAgentContext.d.ts +7 -0
- package/dist/space-sdk/src/hooks/useBackend.d.ts +23 -0
- package/dist/space-sdk/src/hooks/useCloudComputer.d.ts +45 -0
- package/dist/space-sdk/src/hooks/useCloudComputerShell.d.ts +49 -0
- package/dist/space-sdk/src/hooks/useCollectionInteractions.d.ts +118 -0
- package/dist/space-sdk/src/hooks/useDatabaseSchema.d.ts +14 -0
- package/dist/space-sdk/src/hooks/useDatabaseSubscription.d.ts +26 -0
- package/dist/space-sdk/src/hooks/useDocument.d.ts +22 -0
- package/dist/space-sdk/src/hooks/useDocuments.d.ts +31 -0
- package/dist/space-sdk/src/hooks/useHandover.d.ts +43 -0
- package/dist/space-sdk/src/hooks/useLongPressSelection.d.ts +9 -0
- package/dist/space-sdk/src/hooks/useMarqueeSelection.d.ts +57 -0
- package/dist/space-sdk/src/hooks/useMultiSelect.d.ts +51 -0
- package/dist/space-sdk/src/hooks/useNativeDocumentBody.d.ts +24 -0
- package/dist/space-sdk/src/hooks/useNativeDocuments.d.ts +55 -0
- package/dist/space-sdk/src/hooks/useNotis.d.ts +25 -0
- package/dist/space-sdk/src/hooks/useNotisNavigation.d.ts +33 -0
- package/dist/space-sdk/src/hooks/useQuery.d.ts +22 -0
- package/dist/space-sdk/src/hooks/useTool.d.ts +20 -0
- package/dist/space-sdk/src/hooks/useToolQuery.d.ts +3 -0
- package/dist/space-sdk/src/hooks/useTools.d.ts +15 -0
- package/dist/space-sdk/src/hooks/useTopBarSearch.d.ts +48 -0
- package/dist/space-sdk/src/hooks/useTopBarTitleLeaf.d.ts +24 -0
- package/dist/space-sdk/src/hooks/useUpsertDocument.d.ts +27 -0
- package/dist/space-sdk/src/hooks/useViewParams.d.ts +76 -0
- package/dist/space-sdk/src/hooks/useViewerReads.d.ts +38 -0
- package/dist/space-sdk/src/index.d.ts +98 -0
- package/dist/space-sdk/src/interactions/actions.d.ts +33 -0
- package/dist/space-sdk/src/interactions/shortcuts.d.ts +42 -0
- package/dist/space-sdk/src/interactions/visibility.d.ts +2 -0
- package/dist/space-sdk/src/nativeData.d.ts +66 -0
- package/dist/space-sdk/src/provider.d.ts +17 -0
- package/dist/space-sdk/src/queryCache.d.ts +45 -0
- package/dist/space-sdk/src/queryErrors.ts +51 -0
- package/dist/space-sdk/src/runtime.d.ts +615 -0
- package/dist/space-sdk/src/runtimeModules.ts +33 -0
- package/dist/space-sdk/src/space.ts +442 -0
- package/dist/space-sdk/src/standardViewParams.d.ts +15 -0
- package/dist/space-sdk/src/tailwind-candidates.d.ts +3418 -0
- package/dist/space-sdk/src/tailwind.ts +66 -0
- package/dist/space-sdk/src/ui/actions/ActionToast.d.ts +15 -0
- package/dist/space-sdk/src/ui/actions/actionWords.d.ts +16 -0
- package/dist/space-sdk/src/ui/actions/index.d.ts +7 -0
- package/dist/space-sdk/src/ui/actions/useActions.d.ts +69 -0
- package/dist/space-sdk/src/ui/actions/useToast.d.ts +41 -0
- package/dist/space-sdk/src/ui/blocks/Tabs.d.ts +139 -0
- package/dist/space-sdk/src/ui/blocks/blocks.d.ts +217 -0
- package/dist/space-sdk/src/ui/blocks/index.d.ts +4 -0
- package/dist/space-sdk/src/ui/board/BoardView.d.ts +36 -0
- package/dist/space-sdk/src/ui/board/boardModel.d.ts +46 -0
- package/dist/space-sdk/src/ui/board/boardWords.d.ts +9 -0
- package/dist/space-sdk/src/ui/board/index.d.ts +5 -0
- package/dist/space-sdk/src/ui/calendar/CalendarView.d.ts +49 -0
- package/dist/space-sdk/src/ui/calendar/DayPicker.d.ts +23 -0
- package/dist/space-sdk/src/ui/calendar/calendarMath.d.ts +81 -0
- package/dist/space-sdk/src/ui/charts/Chart.d.ts +203 -0
- package/dist/space-sdk/src/ui/charts/ChartTable.d.ts +37 -0
- package/dist/space-sdk/src/ui/charts/RenderChartData.d.ts +16 -0
- package/dist/space-sdk/src/ui/charts/StatRow.d.ts +59 -0
- package/dist/space-sdk/src/ui/charts/chartData.d.ts +84 -0
- package/dist/space-sdk/src/ui/comments/CommentPane.d.ts +58 -0
- package/dist/space-sdk/src/ui/comments/commentModel.d.ts +157 -0
- package/dist/space-sdk/src/ui/comments/commentWords.d.ts +60 -0
- package/dist/space-sdk/src/ui/comments/index.d.ts +4 -0
- package/dist/space-sdk/src/ui/comments/useComments.d.ts +146 -0
- package/dist/space-sdk/src/ui/engine/HelpSheet.d.ts +12 -0
- package/dist/space-sdk/src/ui/engine/KeyboardEngine.d.ts +27 -0
- package/dist/space-sdk/src/ui/engine/core.d.ts +73 -0
- package/dist/space-sdk/src/ui/engine/hooks.d.ts +64 -0
- package/dist/space-sdk/src/ui/engine/index.d.ts +14 -0
- package/dist/space-sdk/src/ui/engine/instance.d.ts +21 -0
- package/dist/space-sdk/src/ui/engine/keycapContext.d.ts +2 -0
- package/dist/space-sdk/src/ui/engine/registry.d.ts +77 -0
- package/dist/space-sdk/src/ui/engine/step.d.ts +11 -0
- package/dist/space-sdk/src/ui/engine/types.d.ts +54 -0
- package/dist/space-sdk/src/ui/fields/fieldWords.d.ts +56 -0
- package/dist/space-sdk/src/ui/fields/fields.d.ts +162 -0
- package/dist/space-sdk/src/ui/fields/index.d.ts +5 -0
- package/dist/space-sdk/src/ui/fields/useFieldDraft.d.ts +22 -0
- package/dist/space-sdk/src/ui/gallery/AttachmentGallery.d.ts +53 -0
- package/dist/space-sdk/src/ui/gallery/ImageViewer.d.ts +27 -0
- package/dist/space-sdk/src/ui/gallery/galleryWords.d.ts +95 -0
- package/dist/space-sdk/src/ui/gallery/index.d.ts +5 -0
- package/dist/space-sdk/src/ui/icons.d.ts +13 -0
- package/dist/space-sdk/src/ui/index.d.ts +54 -0
- package/dist/space-sdk/src/ui/keycap/Keycap.d.ts +22 -0
- package/dist/space-sdk/src/ui/keycap/index.d.ts +2 -0
- package/dist/space-sdk/src/ui/layout/Panels.d.ts +50 -0
- package/dist/space-sdk/src/ui/layout/chatPanelEdge.d.ts +77 -0
- package/dist/space-sdk/src/ui/layout/index.d.ts +5 -0
- package/dist/space-sdk/src/ui/layout/panelMath.d.ts +91 -0
- package/dist/space-sdk/src/ui/list/ListReader.d.ts +380 -0
- package/dist/space-sdk/src/ui/list/Row.d.ts +145 -0
- package/dist/space-sdk/src/ui/list/SelectionBar.d.ts +65 -0
- package/dist/space-sdk/src/ui/list/index.d.ts +14 -0
- package/dist/space-sdk/src/ui/list/rowState.d.ts +22 -0
- package/dist/space-sdk/src/ui/list/useListKeys.d.ts +89 -0
- package/dist/space-sdk/src/ui/list/useReorder.d.ts +86 -0
- package/dist/space-sdk/src/ui/list/useSelection.d.ts +90 -0
- package/dist/space-sdk/src/ui/open/OpenAsMenu.d.ts +15 -0
- package/dist/space-sdk/src/ui/open/index.d.ts +6 -0
- package/dist/space-sdk/src/ui/open/openModeStore.d.ts +39 -0
- package/dist/space-sdk/src/ui/open/useOpenMode.d.ts +17 -0
- package/dist/space-sdk/src/ui/overlay/Menu.d.ts +88 -0
- package/dist/space-sdk/src/ui/overlay/Popover.d.ts +28 -0
- package/dist/space-sdk/src/ui/overlay/Tooltip.d.ts +19 -0
- package/dist/space-sdk/src/ui/overlay/index.d.ts +9 -0
- package/dist/space-sdk/src/ui/overlay/layerRoot.d.ts +24 -0
- package/dist/space-sdk/src/ui/overlay/position.d.ts +31 -0
- package/dist/space-sdk/src/ui/period/PeriodPicker.d.ts +30 -0
- package/dist/space-sdk/src/ui/period/dateFormat.d.ts +50 -0
- package/dist/space-sdk/src/ui/period/dateWords.d.ts +88 -0
- package/dist/space-sdk/src/ui/period/periods.d.ts +70 -0
- package/dist/space-sdk/src/ui/period/usePeriod.d.ts +23 -0
- package/dist/space-sdk/src/ui/present/index.d.ts +3 -0
- package/dist/space-sdk/src/ui/present/presentWords.d.ts +27 -0
- package/dist/space-sdk/src/ui/present/screens.d.ts +36 -0
- package/dist/space-sdk/src/ui/present/useAudienceScreen.d.ts +38 -0
- package/dist/space-sdk/src/ui/styles.d.ts +21 -0
- package/dist/space-sdk/src/ui/timeline/TimelineView.d.ts +44 -0
- package/dist/space-sdk/src/ui/timeline/timelineMath.d.ts +123 -0
- package/dist/space-sdk/src/viewPeriod.d.ts +8 -0
- package/dist/space-sdk/src/vite.ts +92 -0
- package/dist/space-sdk/src/words/actions.ts +295 -0
- package/dist/space-sdk/src/words/clashes.ts +158 -0
- package/dist/space-sdk/src/words/format.ts +136 -0
- package/dist/space-sdk/src/words/index.ts +13 -0
- package/dist/space-sdk/src/words/keymap.ts +72 -0
- package/dist/space-sdk/src/words/keys.ts +297 -0
- package/dist/space-sdk/src/words/shell.ts +46 -0
- package/dist/space-sdk/src/words/types.ts +65 -0
- package/dist/view-renderer/dist/host.css +1 -0
- package/dist/view-renderer/dist/host.js +2374 -0
- package/dist/view-renderer/dist/theme.css +73 -0
- package/dist/view-renderer/src/dom.js +212 -0
- package/dist/view-renderer/src/errors.js +8 -0
- package/dist/view-renderer/src/host.tsx +250 -0
- package/dist/view-renderer/src/index.js +4 -0
- package/dist/view-renderer/src/png.js +213 -0
- package/dist/view-renderer/src/pool.js +110 -0
- package/dist/view-renderer/src/remote-broker.js +59 -0
- package/dist/view-renderer/src/renderer.js +271 -0
- package/package.json +21 -11
- package/skills/notis-cli/AGENT_INSTRUCTIONS.md +63 -12
- package/skills/notis-onboarding/BRIEF.md +14 -9
- package/skills/notis-query/cli.md +1 -1
- package/skills/notis-spaces/cli.md +540 -0
- package/src/cli.js +8 -8
- package/src/command-specs/agents.js +121 -8
- package/src/command-specs/auth.js +3 -3
- package/src/command-specs/diagnostics.js +97 -20
- package/src/command-specs/documents.js +158 -0
- package/src/command-specs/files.js +29 -0
- package/src/command-specs/handover.js +331 -79
- package/src/command-specs/helpers.js +50 -40
- package/src/command-specs/index.js +14 -5
- package/src/command-specs/meta.js +38 -9
- package/src/command-specs/onboarding.js +1 -1
- package/src/command-specs/profile.js +42 -1
- package/src/command-specs/skills.js +314 -9
- package/src/command-specs/smoke.js +16 -4
- package/src/command-specs/spaces.js +783 -0
- package/src/command-specs/tools.js +118 -21
- package/src/command-specs/version-history.js +20 -0
- package/src/command-specs/views.js +69 -0
- package/src/runtime/agent-browser.js +178 -267
- package/src/runtime/agent-setup.js +172 -13
- package/src/runtime/app-boundary-validator.js +338 -2
- package/src/runtime/app-data-literals.js +82 -0
- package/src/runtime/app-platform.js +750 -1570
- package/src/runtime/auth-recovery.js +17 -0
- package/src/runtime/base-skills.d.ts +11 -0
- package/src/runtime/base-skills.js +100 -3
- package/src/runtime/computer-identity.d.ts +30 -0
- package/src/runtime/computer-identity.js +151 -0
- package/src/runtime/config-lock.js +135 -0
- package/src/runtime/confined-space-build.js +74 -0
- package/src/runtime/confined-space-verify.js +49 -0
- package/src/runtime/delegated-context.js +5 -21
- package/src/runtime/native-record-files.js +47 -0
- package/src/runtime/oauth.js +342 -31
- package/src/runtime/profiles.js +19 -112
- package/src/runtime/retired-sdk-files.js +24 -0
- package/src/runtime/retry.js +150 -0
- package/src/runtime/skill-sync/cloud-client.ts +33 -8
- package/src/runtime/skill-sync/index.ts +443 -106
- package/src/runtime/skill-sync/local-scanner.ts +248 -32
- package/src/runtime/skill-sync/native-identity.ts +102 -0
- package/src/runtime/skill-sync/native-plan.ts +70 -0
- package/src/runtime/skill-sync/symlink-manager.ts +66 -16
- package/src/runtime/skill-sync/sync-plan.ts +34 -2
- package/src/runtime/skill-sync/types.ts +54 -0
- package/src/runtime/skill-sync/write-cloud-skill.ts +4 -4
- package/src/runtime/skill-sync-service.js +109 -0
- package/src/runtime/source-root.js +32 -0
- package/src/runtime/space-collection-scaffold.js +107 -0
- package/src/runtime/space-database-structures.js +229 -0
- package/src/runtime/space-fixture-broker.js +68 -0
- package/src/runtime/space-lock.js +337 -0
- package/src/runtime/space-platform.js +133 -0
- package/src/runtime/space-source.js +290 -0
- package/src/runtime/space-test-server.js +354 -0
- package/src/runtime/space-verification.js +94 -0
- package/src/runtime/space-verify-browser-worker.js +51 -0
- package/src/runtime/space-view-manifest.js +368 -0
- package/src/runtime/sync-skills.d.ts +5 -1
- package/src/runtime/sync-skills.js +11 -2
- package/src/runtime/tool-confirmation.js +138 -0
- package/src/runtime/transport.js +118 -24
- package/src/runtime/upsell.js +219 -0
- package/src/runtime/view-render.js +49 -0
- package/src/skill-sync-worker-entry.js +2 -0
- package/src/skill-sync-worker.js +114 -0
- package/src/space-harness-host.ts +122 -0
- package/template/.harness/index.html.tmpl +9 -352
- package/template/app/globals.css +29 -3
- package/template/app/layout.tsx +2 -6
- package/template/components/ui/badge.tsx +7 -4
- package/template/components/ui/button.tsx +1 -1
- package/template/components/ui/native-select.tsx +24 -0
- package/template/notis.config.ts +4 -36
- package/template/package-lock.json +1800 -1867
- package/template/package.json +16 -14
- package/template/postcss.config.mjs +1 -1
- package/template/tailwind.config.ts +1 -6
- package/template/tsconfig.json +1 -0
- package/template/vite.config.ts +2 -2
- package/dist/base-skills/notis-apps/SKILL.md +0 -613
- package/skills/notis-apps/cli.md +0 -285
- package/src/command-specs/apps.js +0 -3011
- package/src/runtime/app-changelog.js +0 -79
- package/src/runtime/app-dev-consumers.js +0 -154
- package/src/runtime/app-dev-host-lock.js +0 -80
- package/src/runtime/app-dev-process-identity.js +0 -111
- package/src/runtime/app-dev-roots.js +0 -284
- package/src/runtime/app-dev-server.js +0 -1140
- package/src/runtime/app-dev-sessions.js +0 -185
- package/src/runtime/app-registry-scaffolds.js +0 -367
- package/src/runtime/assets/store-screenshot-dark.png +0 -0
- package/src/runtime/cli-mode.generated.js +0 -5
- package/src/runtime/cli-mode.js +0 -34
- package/src/runtime/store-screenshot.js +0 -143
- package/template/CHANGELOG.md +0 -5
- package/template/app/page.tsx +0 -60
- package/template/components/ui/card.tsx +0 -56
- package/template/packages/sdk/src/components/DocumentEditor.tsx +0 -103
- package/template/packages/sdk/src/config.ts +0 -259
- package/template/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
- package/template/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
- package/template/packages/sdk/src/hooks/useDocument.ts +0 -78
- package/template/packages/sdk/src/hooks/useNotisNavigation.ts +0 -52
- package/template/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
- package/template/packages/sdk/src/runtime.ts +0 -439
- package/template/packages/sdk/src/vite.ts +0 -56
- package/template/packages/sdk/tsconfig.json +0 -15
- /package/{template/packages → dist}/sdk/src/components/MultiSelectDragOverlay.tsx +0 -0
- /package/{template/packages → dist}/sdk/src/components/ShortcutHints.tsx +0 -0
- /package/{template/packages → dist}/sdk/src/hooks/useActiveResource.ts +0 -0
- /package/{template/packages → dist}/sdk/src/hooks/useTool.ts +0 -0
- /package/{template/packages → dist}/sdk/src/hooks/useTools.ts +0 -0
- /package/{template/packages → dist}/sdk/src/ui.ts +0 -0
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useNotisRuntime } from '../provider';
|
|
4
|
-
import type { AppDescriptor, CollectionItemDetail, DatabaseDescriptor, RouteDescriptor } from '../runtime';
|
|
4
|
+
import type { RuntimeResource, AppDescriptor, CollectionItemDetail, DatabaseDescriptor, RouteDescriptor } from '../runtime';
|
|
5
5
|
|
|
6
6
|
interface NotisContext {
|
|
7
|
+
/** UI selection only; it grants no access and is never a record scope. */
|
|
8
|
+
selection: string | null;
|
|
9
|
+
resource: RuntimeResource | null;
|
|
7
10
|
/** App metadata (id, name, icon, description). Null before runtime loads. */
|
|
8
11
|
app: AppDescriptor | null;
|
|
9
12
|
/** Current route descriptor. Null before runtime loads. */
|
|
@@ -27,6 +30,8 @@ export function useNotis(): NotisContext {
|
|
|
27
30
|
const runtime = useNotisRuntime();
|
|
28
31
|
|
|
29
32
|
return {
|
|
33
|
+
selection: runtime?.context?.selection ?? null,
|
|
34
|
+
resource: runtime?.resource ?? null,
|
|
30
35
|
app: runtime?.app ?? null,
|
|
31
36
|
route: runtime?.route ?? null,
|
|
32
37
|
databases: runtime?.databases ?? [],
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback } from 'react';
|
|
4
|
+
import { useNotisRuntime } from '../provider';
|
|
5
|
+
|
|
6
|
+
export interface SpaceNavigationOptions {
|
|
7
|
+
recordKey?: string | null;
|
|
8
|
+
/** Declared view parameters. The destination authorizes records and drops unknown names. */
|
|
9
|
+
params?: Record<string, string | number | boolean | null | undefined>;
|
|
10
|
+
selection?: string | null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface NavigationActions {
|
|
14
|
+
/** Resolve a declared portable destination under the current viewer's access. */
|
|
15
|
+
toNamedSpace: (alias: string, options?: SpaceNavigationOptions) => Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Open an exact Space, optionally at a native record's stable record_key. Resolves once the host has taken the
|
|
18
|
+
* navigation; rejects when the host refuses it (the Space closed, or the view it came from is no longer active).
|
|
19
|
+
*/
|
|
20
|
+
toSpace: (spaceId: string, options?: SpaceNavigationOptions) => Promise<void>;
|
|
21
|
+
/** Navigate to a route within the app by its path. */
|
|
22
|
+
toRoute: (path: string, options?: { resourceId?: string | null }) => void;
|
|
23
|
+
/** Navigate to the app's default route. */
|
|
24
|
+
toApp: () => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Navigation helpers for Spaces and app routes.
|
|
29
|
+
* When rendered inside the portal, uses the runtime.navigate() bridge.
|
|
30
|
+
* Without a portal runtime, falls back to window.location for route navigation.
|
|
31
|
+
*
|
|
32
|
+
* ```tsx
|
|
33
|
+
* const nav = useNotisNavigation();
|
|
34
|
+
* nav.toSpace(spaceId, { recordKey });
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export function useNotisNavigation(): NavigationActions {
|
|
38
|
+
const runtime = useNotisRuntime();
|
|
39
|
+
|
|
40
|
+
const toNamedSpace = useCallback(async (alias: string, options?: SpaceNavigationOptions) => {
|
|
41
|
+
if (runtime?.resource?.kind !== 'space' || !runtime.navigate) throw new Error('Open this Space in a current Notis host to navigate.');
|
|
42
|
+
await runtime.navigate({ kind: 'space-reference', alias, documentId: options?.recordKey ?? null, selection: options?.selection ?? null,
|
|
43
|
+
...(options?.params ? { params: options.params } : {}) });
|
|
44
|
+
}, [runtime]);
|
|
45
|
+
|
|
46
|
+
const toSpace = useCallback(async (spaceId: string, options?: SpaceNavigationOptions) => {
|
|
47
|
+
if (!runtime?.navigate) throw new Error('Open this Space in a current Notis host to navigate.');
|
|
48
|
+
await runtime.navigate({ kind: 'space', spaceId, documentId: options?.recordKey ?? null,
|
|
49
|
+
...(options?.params ? { params: options.params } : {}), ...(options?.selection ? { selection: options.selection } : {}) });
|
|
50
|
+
}, [runtime]);
|
|
51
|
+
|
|
52
|
+
const toRoute = useCallback((path: string, options?: { resourceId?: string | null }) => {
|
|
53
|
+
if (runtime?.navigate) {
|
|
54
|
+
runtime.navigate({ kind: 'route', path, resourceId: options?.resourceId ?? null });
|
|
55
|
+
} else if (typeof window !== 'undefined') {
|
|
56
|
+
const url = new URL(path, window.location.href);
|
|
57
|
+
if (options?.resourceId) url.searchParams.set('resource', options.resourceId);
|
|
58
|
+
else url.searchParams.delete('resource');
|
|
59
|
+
window.location.href = url.toString();
|
|
60
|
+
}
|
|
61
|
+
}, [runtime]);
|
|
62
|
+
|
|
63
|
+
const toApp = useCallback(() => {
|
|
64
|
+
if (runtime?.navigate) {
|
|
65
|
+
runtime.navigate({ kind: 'app' });
|
|
66
|
+
}
|
|
67
|
+
}, [runtime]);
|
|
68
|
+
|
|
69
|
+
return { toNamedSpace, toSpace, toRoute, toApp };
|
|
70
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useSyncExternalStore } from 'react';
|
|
4
|
+
import { useNotisRuntime } from '../provider';
|
|
5
|
+
import { createQueryClient, queryKey } from '../queryCache';
|
|
6
|
+
import { spaceQueryKey } from '../space';
|
|
7
|
+
import { localizeQueryError } from '../queryErrors';
|
|
8
|
+
|
|
9
|
+
export interface UseQueryOptions {
|
|
10
|
+
/** Required acknowledgement: only idempotent reads may run on mount or prefetch. */
|
|
11
|
+
readOnly: true;
|
|
12
|
+
enabled?: boolean;
|
|
13
|
+
staleTimeMs?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface UseQueryResult<T> {
|
|
17
|
+
data: T | undefined;
|
|
18
|
+
hasData: boolean;
|
|
19
|
+
loading: boolean;
|
|
20
|
+
isFetching: boolean;
|
|
21
|
+
error: Error | null;
|
|
22
|
+
/** Reads again; resolves to the read's localized error, or null when it succeeded (never rejects). */
|
|
23
|
+
refetch: () => Promise<Error | null>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Cached read callback; scope and lifetime are provided by the host, never by app globals. */
|
|
27
|
+
export function useQuery<T>(key: readonly unknown[], read: () => Promise<T>, options: UseQueryOptions): UseQueryResult<T> {
|
|
28
|
+
const runtime = useNotisRuntime();
|
|
29
|
+
// Older hosts deliberately get a hook-local cache: no cross-runtime/account reuse.
|
|
30
|
+
const fallback = useMemo(() => createQueryClient(), [runtime]);
|
|
31
|
+
const client = runtime?.queryClient ?? fallback;
|
|
32
|
+
const cacheKey = queryKey(spaceQueryKey(runtime, key));
|
|
33
|
+
const enabled = Boolean(runtime) && options.enabled !== false && options.readOnly === true;
|
|
34
|
+
const readRef = useRef(read);
|
|
35
|
+
useLayoutEffect(() => { readRef.current = read; });
|
|
36
|
+
const subscribe = useCallback((listener: () => void) => enabled ? client.subscribe(cacheKey, listener) : () => {}, [cacheKey, client, enabled]);
|
|
37
|
+
const snapshot = useSyncExternalStore(subscribe, () => client.getSnapshot<T>(cacheKey), () => client.getSnapshot<T>(cacheKey));
|
|
38
|
+
const fetch = useCallback(() => {
|
|
39
|
+
const capturedRead = readRef.current;
|
|
40
|
+
return client.fetch(cacheKey, capturedRead, { staleTimeMs: options.staleTimeMs });
|
|
41
|
+
}, [cacheKey, client, options.staleTimeMs]);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (!enabled) return;
|
|
44
|
+
void fetch().catch(() => {}); // Error belongs to the snapshot; no unhandled rejection.
|
|
45
|
+
}, [enabled, fetch, snapshot.invalidation]);
|
|
46
|
+
const locale = runtime?.context?.locale;
|
|
47
|
+
const refetch = useCallback(async (): Promise<Error | null> => {
|
|
48
|
+
if (!enabled) return null;
|
|
49
|
+
client.invalidate(cacheKey);
|
|
50
|
+
try {
|
|
51
|
+
await fetch();
|
|
52
|
+
return null;
|
|
53
|
+
} catch (failure) {
|
|
54
|
+
return localizeQueryError(failure instanceof Error ? failure : new Error(String(failure)), locale);
|
|
55
|
+
}
|
|
56
|
+
}, [cacheKey, client, enabled, fetch, locale]);
|
|
57
|
+
const error = useMemo(() => localizeQueryError(snapshot.error, runtime?.context?.locale), [snapshot.error, runtime?.context?.locale]);
|
|
58
|
+
return {
|
|
59
|
+
data: enabled ? snapshot.data : undefined,
|
|
60
|
+
hasData: enabled && snapshot.hasData,
|
|
61
|
+
loading: enabled && !snapshot.hasData && !snapshot.error,
|
|
62
|
+
isFetching: enabled && snapshot.isFetching,
|
|
63
|
+
error: enabled ? error : null,
|
|
64
|
+
refetch,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Opt-in preparation for small, known read queries. Never use for writes or full-database crawls. */
|
|
69
|
+
export function useQueryClient(): {
|
|
70
|
+
invalidate(key?: readonly unknown[]): void;
|
|
71
|
+
prefetch<T>(key: readonly unknown[], read: () => Promise<T>, options: UseQueryOptions): Promise<T | undefined>;
|
|
72
|
+
} {
|
|
73
|
+
const runtime = useNotisRuntime();
|
|
74
|
+
return useMemo(() => ({
|
|
75
|
+
invalidate: (key?: readonly unknown[]) => runtime?.queryClient?.invalidate(key ? queryKey(spaceQueryKey(runtime, key)) : undefined),
|
|
76
|
+
prefetch: <T,>(key: readonly unknown[], read: () => Promise<T>, options: UseQueryOptions) => {
|
|
77
|
+
if (!runtime?.queryClient || options.enabled === false || options.readOnly !== true) return Promise.resolve(undefined);
|
|
78
|
+
return runtime.queryClient.prefetch(queryKey(spaceQueryKey(runtime, key)), read, { staleTimeMs: options.staleTimeMs });
|
|
79
|
+
},
|
|
80
|
+
}), [runtime]);
|
|
81
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useNotisRuntime } from '../provider';
|
|
4
|
+
import { useQuery, type UseQueryOptions } from './useQuery';
|
|
5
|
+
|
|
6
|
+
/** An explicitly identified read-only tool, cached by tool name and exact arguments. */
|
|
7
|
+
export function useToolQuery<T = unknown>(name: string, args: Record<string, unknown> = {}, options: UseQueryOptions) {
|
|
8
|
+
const runtime = useNotisRuntime();
|
|
9
|
+
return useQuery<T>(['tool', name, args], () => runtime!.callTool<T>(name, args, {
|
|
10
|
+
readOnly: true, dedupe: true,
|
|
11
|
+
}), options);
|
|
12
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useLayoutEffect, useRef } from 'react';
|
|
4
|
+
import { useNotisRuntime } from '../provider';
|
|
5
|
+
|
|
6
|
+
interface TopBarSearchOptions {
|
|
7
|
+
/** Current value shown in the top bar search input. */
|
|
8
|
+
value: string;
|
|
9
|
+
/** Called when the user types in the top bar. */
|
|
10
|
+
onChange: (value: string) => void;
|
|
11
|
+
/** Placeholder text for the input. Defaults to 'Search…'. */
|
|
12
|
+
placeholder?: string;
|
|
13
|
+
/** Called when the user presses Enter. May return a Promise. */
|
|
14
|
+
onSubmit?: () => void | Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** A view that never takes the typed values keeps at most this many waiting. */
|
|
18
|
+
const MAX_UNSHOWN = 64;
|
|
19
|
+
|
|
20
|
+
interface TopBarSearchActions {
|
|
21
|
+
/** Toggle the loader that replaces the search icon in the top bar. */
|
|
22
|
+
setLoading: (loading: boolean) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Registers this view's search binding with the portal's top bar search input.
|
|
27
|
+
* While the component is mounted, typing in the top bar calls `onChange`,
|
|
28
|
+
* pressing Enter calls `onSubmit`, and the `value` prop drives the input.
|
|
29
|
+
*
|
|
30
|
+
* Use `setLoading(true)` only for an explicitly submitted search action, then
|
|
31
|
+
* restore the icon with `setLoading(false)`. Automatic reads use content
|
|
32
|
+
* skeletons initially and refresh populated content silently.
|
|
33
|
+
*
|
|
34
|
+
* Without a portal runtime, this hook is a safe no-op.
|
|
35
|
+
*
|
|
36
|
+
* ```tsx
|
|
37
|
+
* const [q, setQ] = useState('');
|
|
38
|
+
* const { setLoading } = useTopBarSearch({
|
|
39
|
+
* value: q,
|
|
40
|
+
* onChange: setQ,
|
|
41
|
+
* placeholder: 'Search notes…',
|
|
42
|
+
* onSubmit: async () => {
|
|
43
|
+
* setLoading(true);
|
|
44
|
+
* try { await runSearch(q); } finally { setLoading(false); }
|
|
45
|
+
* },
|
|
46
|
+
* });
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* Callback refs retain the latest committed handlers without re-registering
|
|
50
|
+
* search on every app render. Inline callbacks are safe.
|
|
51
|
+
*
|
|
52
|
+
* The top bar owns what is typed in it. A `value` that came from the bar is
|
|
53
|
+
* not sent back: the view's answer can arrive after the next key, and sending
|
|
54
|
+
* it back would undo that key. A value the view sets itself (a clear button,
|
|
55
|
+
* a link, a value it trims) is sent to the bar.
|
|
56
|
+
*/
|
|
57
|
+
export function useTopBarSearch(opts: TopBarSearchOptions): TopBarSearchActions {
|
|
58
|
+
const runtime = useNotisRuntime();
|
|
59
|
+
const { value, onChange, placeholder, onSubmit } = opts;
|
|
60
|
+
const callbacks = useRef({ onChange, onSubmit });
|
|
61
|
+
useLayoutEffect(() => { callbacks.current = { onChange, onSubmit }; });
|
|
62
|
+
// Values the bar sent that the view has not shown yet, oldest first.
|
|
63
|
+
const fromBar = useRef<string[]>([]);
|
|
64
|
+
const hasSubmit = Boolean(onSubmit);
|
|
65
|
+
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
const register = runtime?.registerTopBarSearch;
|
|
68
|
+
if (!register) return;
|
|
69
|
+
register({
|
|
70
|
+
onChange: (next) => {
|
|
71
|
+
const sent = fromBar.current;
|
|
72
|
+
sent.push(next);
|
|
73
|
+
if (sent.length > MAX_UNSHOWN) sent.splice(0, sent.length - MAX_UNSHOWN);
|
|
74
|
+
callbacks.current.onChange(next);
|
|
75
|
+
},
|
|
76
|
+
placeholder,
|
|
77
|
+
onSubmit: hasSubmit ? () => callbacks.current.onSubmit?.() : undefined,
|
|
78
|
+
});
|
|
79
|
+
return () => {
|
|
80
|
+
register(null);
|
|
81
|
+
};
|
|
82
|
+
}, [runtime, placeholder, hasSubmit]);
|
|
83
|
+
|
|
84
|
+
useLayoutEffect(() => {
|
|
85
|
+
const sent = fromBar.current;
|
|
86
|
+
// The view caught up with the bar, or shows a value the bar has already gone past.
|
|
87
|
+
if (sent.length && value === sent[sent.length - 1]) { sent.length = 0; return; }
|
|
88
|
+
const shown = sent.indexOf(value);
|
|
89
|
+
if (shown >= 0) { sent.splice(0, shown + 1); return; }
|
|
90
|
+
sent.length = 0;
|
|
91
|
+
runtime?.setTopBarSearchValue?.(value);
|
|
92
|
+
}, [runtime, value]);
|
|
93
|
+
|
|
94
|
+
const setLoading = useCallback(
|
|
95
|
+
(loading: boolean) => {
|
|
96
|
+
runtime?.setTopBarSearchLoading?.(loading);
|
|
97
|
+
},
|
|
98
|
+
[runtime],
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
return { setLoading };
|
|
102
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef } from 'react';
|
|
4
|
+
import { useNotisRuntime } from '../provider';
|
|
5
|
+
|
|
6
|
+
type Leaf = { label: string; onBack?: () => void; recordKey?: string };
|
|
7
|
+
type LeafSetter = (leaf: Leaf | null) => void;
|
|
8
|
+
type Owner = { label: string; back: { current: (() => void) | undefined }; hasBack: boolean; recordKey: string | null };
|
|
9
|
+
type LeafOwners = { owners: Owner[]; sent: Owner | null; back: () => void };
|
|
10
|
+
|
|
11
|
+
// The components that own a leaf, per host setter, in mount order. The newest owner names the leaf; a component
|
|
12
|
+
// with no record open owns nothing and never writes, so it cannot clear another one's leaf.
|
|
13
|
+
const ownersBySetter = new WeakMap<LeafSetter, LeafOwners>();
|
|
14
|
+
|
|
15
|
+
function publish(set: LeafSetter, entry: LeafOwners) {
|
|
16
|
+
const top = entry.owners.length ? entry.owners[entry.owners.length - 1] : null;
|
|
17
|
+
const sent = entry.sent;
|
|
18
|
+
if (top === sent || (top && sent && top.label === sent.label && top.hasBack === sent.hasBack && top.recordKey === sent.recordKey)) {
|
|
19
|
+
entry.sent = top;
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
entry.sent = top;
|
|
23
|
+
// The host keeps one stable way back per setter; it always calls the newest owner's latest onBack.
|
|
24
|
+
set(top ? { label: top.label, ...(top.hasBack ? { onBack: entry.back } : {}), ...(top.recordKey ? { recordKey: top.recordKey } : {}) } : null);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Adds the open record's label as the last crumb of the Portal top bar title
|
|
29
|
+
* ("Product / Tickets / PRO-002") while the component is mounted. Pass `null`
|
|
30
|
+
* when no record is open: the component then owns no leaf and leaves another
|
|
31
|
+
* component's leaf alone. With several owners, the most recently opened one
|
|
32
|
+
* names the leaf; closing it gives the leaf back to the one before.
|
|
33
|
+
*
|
|
34
|
+
* `onBack` closes the record: the crumb before the leaf (the view) then calls it,
|
|
35
|
+
* so the top bar is the page's way back and the page draws no breadcrumb of its own.
|
|
36
|
+
*
|
|
37
|
+
* `recordKey` names the record the leaf shows, when it is a record of the Space's own
|
|
38
|
+
* collection (its `collection` database): its Editors can then rename that record from
|
|
39
|
+
* the leaf in the top bar, and the view shows the new name once its data refreshes.
|
|
40
|
+
*
|
|
41
|
+
* Without a portal runtime, this hook is a safe no-op.
|
|
42
|
+
*
|
|
43
|
+
* ```tsx
|
|
44
|
+
* useTopBarTitleLeaf(openTicket ? openTicket.key : null, () => setParams({ ticket: null }));
|
|
45
|
+
* useTopBarTitleLeaf(deck ? deck.name : null, undefined, { recordKey: deck?.recordKey });
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export function useTopBarTitleLeaf(label: string | null, onBack?: () => void, options?: { recordKey?: string | null }): void {
|
|
49
|
+
const runtime = useNotisRuntime();
|
|
50
|
+
const leaf = label?.trim() || null;
|
|
51
|
+
const back = useRef(onBack);
|
|
52
|
+
back.current = onBack;
|
|
53
|
+
const hasBack = Boolean(onBack);
|
|
54
|
+
const recordKey = options?.recordKey?.trim() || null;
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
const set = runtime?.setTopBarTitleLeaf;
|
|
58
|
+
if (!set || !leaf) return;
|
|
59
|
+
let entry = ownersBySetter.get(set);
|
|
60
|
+
if (!entry) {
|
|
61
|
+
const created: LeafOwners = { owners: [], sent: null, back: () => created.sent?.back.current?.() };
|
|
62
|
+
entry = created;
|
|
63
|
+
ownersBySetter.set(set, entry);
|
|
64
|
+
}
|
|
65
|
+
const owner: Owner = { label: leaf, back, hasBack, recordKey };
|
|
66
|
+
entry.owners.push(owner);
|
|
67
|
+
publish(set, entry);
|
|
68
|
+
return () => {
|
|
69
|
+
const current = ownersBySetter.get(set);
|
|
70
|
+
if (!current) return;
|
|
71
|
+
current.owners = current.owners.filter((item) => item !== owner);
|
|
72
|
+
publish(set, current);
|
|
73
|
+
};
|
|
74
|
+
}, [runtime, leaf, hasBack, recordKey]);
|
|
75
|
+
}
|
|
@@ -79,7 +79,7 @@ export function useUpsertDocument(databaseSlug: string): UseUpsertDocumentResult
|
|
|
79
79
|
if (!result.document) {
|
|
80
80
|
throw new Error(result.error ?? result.message ?? 'Failed to save document');
|
|
81
81
|
}
|
|
82
|
-
return normalizeDocumentRecord(result.document);
|
|
82
|
+
return normalizeDocumentRecord(result.document, { locale: runtime.context?.locale === 'fr' ? 'fr' : 'en' });
|
|
83
83
|
} catch (err) {
|
|
84
84
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
85
85
|
setError(e);
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback, useMemo } from 'react';
|
|
4
|
+
import { useNotisRuntime } from '../provider';
|
|
5
|
+
import type { NotisRuntime } from '../runtime';
|
|
6
|
+
import { useQuery, type UseQueryResult } from './useQuery';
|
|
7
|
+
import { SdkQueryError } from '../queryErrors';
|
|
8
|
+
import type { SpaceDefinition, SpaceShownOptions, SpaceShownResult, SpaceViewParam } from '../space';
|
|
9
|
+
import { isViewDate, isViewPeriod } from '../viewPeriod';
|
|
10
|
+
|
|
11
|
+
type ParamValue<P> = P extends { type: 'enum'; values: readonly (infer V)[] } ? V
|
|
12
|
+
: P extends { type: 'number' } ? number : P extends { type: 'boolean' } ? boolean : string;
|
|
13
|
+
|
|
14
|
+
/** Typed params of a definition: `useViewParams<typeof definition>()`. */
|
|
15
|
+
export type ViewParamsOf<D extends Pick<SpaceDefinition, 'params'>> = D['params'] extends Record<string, SpaceViewParam>
|
|
16
|
+
? { [K in keyof D['params']]?: ParamValue<D['params'][K]> } : Record<string, string | number | boolean | undefined>;
|
|
17
|
+
|
|
18
|
+
/** A change to the view's URL params: a value sets that param, null or undefined removes it. */
|
|
19
|
+
export type ViewParamChanges<T> = { [K in keyof T]?: T[K] | null };
|
|
20
|
+
|
|
21
|
+
export interface SetViewParamsOptions {
|
|
22
|
+
/** Replace the current history entry instead of adding one, so Back skips this change. Default false. */
|
|
23
|
+
replace?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ViewParams<T> {
|
|
27
|
+
/** Declared params only, typed; invalid URL values are left out (their default applies). */
|
|
28
|
+
params: T;
|
|
29
|
+
/** Declared params whose URL value was invalid and ignored. */
|
|
30
|
+
invalid: string[];
|
|
31
|
+
/** Required params without a value. */
|
|
32
|
+
missing: string[];
|
|
33
|
+
declared: Record<string, SpaceViewParam>;
|
|
34
|
+
ready: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Change this view's own URL params; the same Space and record stay open. Names left out of `changes` keep their
|
|
37
|
+
* value. No destination lookup and no alias to declare: use `useNotisNavigation().toNamedSpace` for another Space.
|
|
38
|
+
* Undeclared names are dropped by the host. Rejects outside a Space.
|
|
39
|
+
*/
|
|
40
|
+
setParams: (changes: ViewParamChanges<T>, options?: SetViewParamsOptions) => Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const RECORD_KEY = /^(?:[0-9a-f]{32}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
|
|
44
|
+
const NUMBER = /^-?\d+(?:\.\d+)?$/;
|
|
45
|
+
// eslint-disable-next-line no-control-regex
|
|
46
|
+
const CONTROL = /[\u0000-\u001f\u007f]/;
|
|
47
|
+
|
|
48
|
+
function parseValue(declaration: SpaceViewParam, raw: string): string | number | boolean | undefined {
|
|
49
|
+
switch (declaration.type) {
|
|
50
|
+
case 'text': return raw.length <= 500 && !CONTROL.test(raw) ? raw : undefined;
|
|
51
|
+
case 'enum': return declaration.values?.includes(raw) ? raw : undefined;
|
|
52
|
+
case 'number': { const value = Number(raw); return NUMBER.test(raw) && Number.isFinite(value) ? value : undefined; }
|
|
53
|
+
case 'boolean': return raw === 'true' ? true : raw === 'false' ? false : undefined;
|
|
54
|
+
case 'date': {
|
|
55
|
+
return ['today', 'tomorrow'].includes(raw) || isViewDate(raw) ? raw : undefined;
|
|
56
|
+
}
|
|
57
|
+
case 'period': return isViewPeriod(raw) ? raw : undefined;
|
|
58
|
+
case 'record': {
|
|
59
|
+
if (!RECORD_KEY.test(raw)) return undefined;
|
|
60
|
+
const hex = raw.replaceAll('-', '').toLowerCase();
|
|
61
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
62
|
+
}
|
|
63
|
+
default: return undefined;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Keep declared, valid values only; unknown params are dropped and defaults applied. */
|
|
68
|
+
export function parseViewParams(declared: Record<string, SpaceViewParam> | undefined,
|
|
69
|
+
raw: Record<string, string> | null | undefined): Pick<ViewParams<Record<string, string | number | boolean>>, 'params' | 'invalid' | 'missing'> {
|
|
70
|
+
const params: Record<string, string | number | boolean> = {};
|
|
71
|
+
const invalid: string[] = [], missing: string[] = [];
|
|
72
|
+
for (const [name, declaration] of Object.entries(declared || {})) {
|
|
73
|
+
const value = raw && Object.hasOwn(raw, name) && typeof raw[name] === 'string' && raw[name] !== '' ? raw[name] : undefined;
|
|
74
|
+
const parsed = value === undefined ? undefined : parseValue(declaration, value);
|
|
75
|
+
if (value !== undefined && parsed === undefined) invalid.push(name);
|
|
76
|
+
if (parsed !== undefined) params[name] = parsed;
|
|
77
|
+
else if (declaration.default !== undefined) params[name] = declaration.default;
|
|
78
|
+
else if (declaration.required) missing.push(name);
|
|
79
|
+
}
|
|
80
|
+
return { params, invalid, missing };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// The params a view asked for last, until the host answers with new ones: two changes in one tick keep each other.
|
|
84
|
+
const pendingWrites = new WeakMap<object, { base: Record<string, string> | null; params: Record<string, string> }>();
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Write the open Space's own URL params (`setParams` of useViewParams; the toolkit's open item and period use it).
|
|
88
|
+
* The host keeps the Space and record, checks no destination, and adds a history entry unless `replace` is set.
|
|
89
|
+
*/
|
|
90
|
+
export function writeViewParams(runtime: NotisRuntime | null | undefined, changes: Record<string, unknown>,
|
|
91
|
+
options: SetViewParamsOptions = {}): Promise<void> {
|
|
92
|
+
if (runtime?.resource?.kind !== 'space' || typeof runtime.navigate !== 'function') {
|
|
93
|
+
return Promise.reject(new Error('Open this Space in a current Notis host to change its params.'));
|
|
94
|
+
}
|
|
95
|
+
const current = runtime.context?.params ?? null;
|
|
96
|
+
const pending = pendingWrites.get(runtime);
|
|
97
|
+
const next: Record<string, string> = { ...(pending && pending.base === current ? pending.params : current ?? {}) };
|
|
98
|
+
for (const [name, value] of Object.entries(changes)) {
|
|
99
|
+
if (value === null || value === undefined) delete next[name];
|
|
100
|
+
else next[name] = String(value);
|
|
101
|
+
}
|
|
102
|
+
pendingWrites.set(runtime, { base: current, params: next });
|
|
103
|
+
const forget = (error: unknown) => {
|
|
104
|
+
if (pendingWrites.get(runtime)?.params === next) pendingWrites.delete(runtime);
|
|
105
|
+
throw error;
|
|
106
|
+
};
|
|
107
|
+
try {
|
|
108
|
+
return Promise.resolve(runtime.navigate({ kind: 'view-params', params: next, ...(options.replace ? { replace: true } : {}) }))
|
|
109
|
+
.then(() => undefined, forget);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
return Promise.reject(error).catch(forget);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The view's declared URL params, typed and validated, and `setParams` to change them. Unknown params (such as
|
|
117
|
+
* `?foo=`) never reach the view. Record params are record keys; the server authorizes them where it uses them.
|
|
118
|
+
*/
|
|
119
|
+
export function useViewParams<D extends Pick<SpaceDefinition, 'params'> = SpaceDefinition>(): ViewParams<ViewParamsOf<D>> {
|
|
120
|
+
const runtime = useNotisRuntime();
|
|
121
|
+
const declared = runtime?.space?.params;
|
|
122
|
+
const raw = runtime?.context?.params;
|
|
123
|
+
const setParams = useCallback((changes: ViewParamChanges<ViewParamsOf<D>>, options?: SetViewParamsOptions) =>
|
|
124
|
+
writeViewParams(runtime, changes as Record<string, unknown>, options), [runtime]);
|
|
125
|
+
return useMemo(() => ({ ...parseViewParams(declared, raw), declared: declared || {}, ready: runtime !== null, setParams }) as
|
|
126
|
+
ViewParams<ViewParamsOf<D>>, [declared, raw, runtime, setParams]);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface UseShownOptions extends SpaceShownOptions {
|
|
130
|
+
enabled?: boolean;
|
|
131
|
+
staleTimeMs?: number;
|
|
132
|
+
/**
|
|
133
|
+
* Read every page and answer with all rows: pages of `pageSize` (default 200) are read from `offset` until one
|
|
134
|
+
* comes back short, up to `SHOWN_ALL_PAGES_MAX` pages.
|
|
135
|
+
*/
|
|
136
|
+
allPages?: boolean;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** The most pages `useShown({ allPages: true })` reads. */
|
|
140
|
+
export const SHOWN_ALL_PAGES_MAX = 50;
|
|
141
|
+
const SHOWN_ALL_PAGES_SIZE = 200;
|
|
142
|
+
|
|
143
|
+
/** True when a declared list has an authorized host reader (Editor or exact record Site). */
|
|
144
|
+
export function useShownAvailable(name: string): boolean {
|
|
145
|
+
const runtime = useNotisRuntime();
|
|
146
|
+
const list = runtime?.space?.shows && Object.hasOwn(runtime.space.shows, name) ? runtime.space.shows[name] : null;
|
|
147
|
+
return runtime?.resource?.kind === 'space' && typeof runtime.shown === 'function' && Boolean(list && 'database' in list);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Runs exactly the list this view declares in `shows` with the current params, so the declaration
|
|
152
|
+
* is the list the view displays. Only sorting and paging are chosen here.
|
|
153
|
+
*
|
|
154
|
+
* A list this view context cannot read (undeclared, described only, or no host reader) answers
|
|
155
|
+
* with `error` instead of a query that never starts, so the view can show an error state. Missing
|
|
156
|
+
* required params are not an error: they are listed by `useViewParams().missing`.
|
|
157
|
+
*/
|
|
158
|
+
export function useShown<TRow = Record<string, unknown>>(name: string, options: UseShownOptions = {}):
|
|
159
|
+
UseQueryResult<SpaceShownResult & { rows: TRow[] }> {
|
|
160
|
+
const runtime = useNotisRuntime();
|
|
161
|
+
const available = useShownAvailable(name);
|
|
162
|
+
const { params, missing } = useViewParams();
|
|
163
|
+
const { sorts, pageSize, offset, includeContent, allPages = false } = options;
|
|
164
|
+
const list = runtime?.space?.shows && Object.hasOwn(runtime.space.shows, name) ? runtime.space.shows[name] : null;
|
|
165
|
+
const reads = list && 'database' in list && Array.isArray(list.params) ? list.params : null;
|
|
166
|
+
// Rows are keyed by the params the list reads: picking another record in the view keeps the rows
|
|
167
|
+
// already shown instead of reading the same list again. The read itself always sends every param.
|
|
168
|
+
const keyed = useMemo(() => reads ? Object.fromEntries(Object.entries(params).filter(([param]) => reads.includes(param))) : params,
|
|
169
|
+
[params, reads]);
|
|
170
|
+
const result = useQuery(['shown', name, keyed, sorts ?? null, pageSize ?? null, offset ?? null, includeContent ?? false,
|
|
171
|
+
allPages], async () => {
|
|
172
|
+
if (!runtime?.shown) throw new SdkQueryError('listUnavailable');
|
|
173
|
+
if (!allPages) {
|
|
174
|
+
return runtime.shown<SpaceShownResult & { rows: TRow[] }>(name, params as Record<string, unknown>,
|
|
175
|
+
{ sorts, pageSize, offset, includeContent });
|
|
176
|
+
}
|
|
177
|
+
const size = pageSize ?? SHOWN_ALL_PAGES_SIZE;
|
|
178
|
+
let first: (SpaceShownResult & { rows: TRow[] }) | null = null;
|
|
179
|
+
const rows: TRow[] = [];
|
|
180
|
+
for (let page = 0; page < SHOWN_ALL_PAGES_MAX; page += 1) {
|
|
181
|
+
const answer = await runtime.shown<SpaceShownResult & { rows: TRow[] }>(name, params as Record<string, unknown>,
|
|
182
|
+
{ sorts, pageSize: size, offset: (offset ?? 0) + page * size, includeContent });
|
|
183
|
+
first ??= answer;
|
|
184
|
+
const pageRows = Array.isArray(answer?.rows) ? answer.rows : [];
|
|
185
|
+
rows.push(...pageRows);
|
|
186
|
+
if (pageRows.length < size) break;
|
|
187
|
+
}
|
|
188
|
+
const merged = { ...(first as SpaceShownResult & { rows: TRow[] }), rows } as SpaceShownResult & { rows: TRow[] };
|
|
189
|
+
return merged;
|
|
190
|
+
}, { readOnly: true, enabled: available && !missing.length && options.enabled !== false, staleTimeMs: options.staleTimeMs });
|
|
191
|
+
const unavailable = runtime !== null && !available && options.enabled !== false;
|
|
192
|
+
const error = useMemo(() => unavailable ? new SdkQueryError('listUnavailable', `The list "${name}" is unavailable in this view: declare it in \`shows\` `
|
|
193
|
+
+ `with a database, or gate it on useShownAvailable("${name}").`, runtime?.context?.locale) : null, [unavailable, name, runtime?.context?.locale]);
|
|
194
|
+
return unavailable ? { ...result, loading: false, isFetching: false, error } : result;
|
|
195
|
+
}
|