@notis_ai/cli 0.2.18 → 0.2.20
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 +279 -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,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { useQuery } from './useQuery';
|
|
4
4
|
import { useNotisRuntime } from '../provider';
|
|
5
5
|
import { normalizeDocumentRecord } from '../documents';
|
|
6
6
|
import type { DocumentRecord, QueryFilter } from '../runtime';
|
|
@@ -19,14 +19,19 @@ export interface UseDocumentsOptions {
|
|
|
19
19
|
offset?: number;
|
|
20
20
|
/** Fetch every page, starting at offset, instead of returning only one page. */
|
|
21
21
|
fetchAll?: boolean;
|
|
22
|
+
/** App-view lists can omit bodies until opening/searching a document. Default: true. */
|
|
23
|
+
includeContent?: boolean;
|
|
22
24
|
enabled?: boolean;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
export interface UseDocumentsResult {
|
|
26
28
|
documents: DocumentRecord[];
|
|
27
29
|
loading: boolean;
|
|
30
|
+
isFetching: boolean;
|
|
31
|
+
hasData: boolean;
|
|
28
32
|
error: Error | null;
|
|
29
|
-
|
|
33
|
+
/** Reads again; resolves to the read's error, or null when it succeeded. */
|
|
34
|
+
refetch: () => Promise<Error | null>;
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
/**
|
|
@@ -44,78 +49,39 @@ export function useDocuments(
|
|
|
44
49
|
options: UseDocumentsOptions = {},
|
|
45
50
|
): UseDocumentsResult {
|
|
46
51
|
const runtime = useNotisRuntime();
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const filterKey = JSON.stringify(options.filter ?? null);
|
|
54
|
-
|
|
55
|
-
const refetch = useCallback(() => {
|
|
56
|
-
setFetchKey((key) => key + 1);
|
|
57
|
-
}, []);
|
|
58
|
-
|
|
59
|
-
useEffect(() => {
|
|
60
|
-
if (!runtime || !enabled) {
|
|
61
|
-
setLoading(false);
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
let cancelled = false;
|
|
66
|
-
setLoading(true);
|
|
67
|
-
setError(null);
|
|
68
|
-
|
|
69
|
-
const filter = filterKey === 'null' ? null : (JSON.parse(filterKey) as QueryFilter);
|
|
70
|
-
|
|
71
|
-
const fetchDocuments = async (): Promise<unknown[]> => {
|
|
52
|
+
// An untitled document reads "Untitled" in the Space's language.
|
|
53
|
+
const locale = runtime?.context?.locale === 'fr' ? 'fr' : 'en';
|
|
54
|
+
const query = useQuery<DocumentRecord[]>(
|
|
55
|
+
['documents', databaseSlug, options.filter ?? null, options.pageSize ?? null, options.offset ?? 0, Boolean(options.fetchAll), options.includeContent !== false, locale],
|
|
56
|
+
async () => {
|
|
57
|
+
if (!runtime) throw new Error('Notis runtime not available');
|
|
72
58
|
const allDocuments: unknown[] = [];
|
|
73
59
|
let offset = options.offset ?? 0;
|
|
74
|
-
|
|
75
60
|
while (true) {
|
|
76
61
|
const result = await runtime.callTool<QueryDatabaseResult>('LOCAL_NOTIS_DATABASE_QUERY', {
|
|
77
62
|
database_slug: databaseSlug,
|
|
78
63
|
query: {
|
|
79
|
-
...(filter ?? {}),
|
|
64
|
+
...(options.filter ?? {}),
|
|
80
65
|
...(options.pageSize !== undefined ? { page_size: options.pageSize } : {}),
|
|
66
|
+
...(options.includeContent === false ? { include_content: false } : {}),
|
|
81
67
|
},
|
|
82
68
|
...(offset > 0 ? { offset } : {}),
|
|
83
|
-
});
|
|
69
|
+
}, { dedupe: true, readOnly: true });
|
|
84
70
|
const message = result.error ?? result.message;
|
|
85
|
-
if (!result.documents && message)
|
|
86
|
-
throw new Error(message);
|
|
87
|
-
}
|
|
71
|
+
if (!result.documents && message) throw new Error(message);
|
|
88
72
|
allDocuments.push(...(result.documents ?? []));
|
|
89
|
-
if (!options.fetchAll || !result.has_more)
|
|
90
|
-
|
|
73
|
+
if (!options.fetchAll || !result.has_more) break;
|
|
91
74
|
const nextOffset = result.next_offset;
|
|
92
75
|
if (typeof nextOffset !== 'number' || nextOffset <= offset) {
|
|
93
76
|
throw new Error('Database query returned an invalid pagination offset');
|
|
94
77
|
}
|
|
95
78
|
offset = nextOffset;
|
|
96
79
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
setDocuments(
|
|
103
|
-
result
|
|
104
|
-
.map(normalizeDocumentRecord)
|
|
105
|
-
.filter((document) => document.id),
|
|
106
|
-
);
|
|
107
|
-
setLoading(false);
|
|
108
|
-
})
|
|
109
|
-
.catch((err) => {
|
|
110
|
-
if (cancelled) return;
|
|
111
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
112
|
-
setLoading(false);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
return () => {
|
|
116
|
-
cancelled = true;
|
|
117
|
-
};
|
|
118
|
-
}, [runtime, databaseSlug, enabled, fetchKey, filterKey, options.fetchAll, options.offset, options.pageSize]);
|
|
119
|
-
|
|
120
|
-
return { documents, loading, error, refetch };
|
|
80
|
+
return allDocuments.map((document) => normalizeDocumentRecord(document, { locale })).filter((document) => document.id);
|
|
81
|
+
},
|
|
82
|
+
{ readOnly: true, enabled: options.enabled },
|
|
83
|
+
);
|
|
84
|
+
return { documents: query.data ?? EMPTY_DOCUMENTS, loading: query.loading, isFetching: query.isFetching, hasData: query.hasData, error: query.error, refetch: query.refetch };
|
|
121
85
|
}
|
|
86
|
+
|
|
87
|
+
const EMPTY_DOCUMENTS: DocumentRecord[] = [];
|
|
@@ -43,18 +43,18 @@ export interface UseHandoverResult {
|
|
|
43
43
|
* feedback button should let the user write in their own words.
|
|
44
44
|
*
|
|
45
45
|
* Pass `skill` to bind the work to a skill declared in `notis.config.ts`; the
|
|
46
|
-
* host rejects a key the app does not declare. `autoSend`
|
|
47
|
-
*
|
|
48
|
-
* user press send.
|
|
46
|
+
* host rejects a key the app does not declare. `autoSend` submits the prepared
|
|
47
|
+
* message on an active user gesture; background calls still open a draft.
|
|
49
48
|
*/
|
|
50
49
|
export function useHandover(): UseHandoverResult {
|
|
51
50
|
const runtime = useNotisRuntime();
|
|
51
|
+
const dispatch = runtime?.resource?.kind === 'space' ? runtime.draftHandover : runtime?.handover;
|
|
52
52
|
const [pending, setPending] = useState(false);
|
|
53
53
|
const [error, setError] = useState<Error | null>(null);
|
|
54
54
|
|
|
55
55
|
const handover = useCallback(
|
|
56
56
|
async (payload: HandoverPayload): Promise<HandoverResult> => {
|
|
57
|
-
if (!
|
|
57
|
+
if (!dispatch) {
|
|
58
58
|
throw new Error('This Notis host cannot hand work to the manager chat.');
|
|
59
59
|
}
|
|
60
60
|
|
|
@@ -62,7 +62,7 @@ export function useHandover(): UseHandoverResult {
|
|
|
62
62
|
setError(null);
|
|
63
63
|
|
|
64
64
|
try {
|
|
65
|
-
return await
|
|
65
|
+
return await dispatch(payload);
|
|
66
66
|
} catch (err) {
|
|
67
67
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
68
68
|
setError(e);
|
|
@@ -71,8 +71,8 @@ export function useHandover(): UseHandoverResult {
|
|
|
71
71
|
setPending(false);
|
|
72
72
|
}
|
|
73
73
|
},
|
|
74
|
-
[
|
|
74
|
+
[dispatch],
|
|
75
75
|
);
|
|
76
76
|
|
|
77
|
-
return { handover, pending, error, available: Boolean(
|
|
77
|
+
return { handover, pending, error, available: Boolean(dispatch) };
|
|
78
78
|
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
4
|
+
import type {
|
|
5
|
+
MouseEvent as ReactMouseEvent,
|
|
6
|
+
PointerEvent as ReactPointerEvent,
|
|
7
|
+
} from 'react';
|
|
8
|
+
|
|
9
|
+
const LONG_PRESS_MS = 500;
|
|
10
|
+
const MOVE_TOLERANCE_PX = 10;
|
|
11
|
+
const INTERACTIVE_SELECTOR = [
|
|
12
|
+
'button',
|
|
13
|
+
'a[href]',
|
|
14
|
+
'input',
|
|
15
|
+
'textarea',
|
|
16
|
+
'select',
|
|
17
|
+
'summary',
|
|
18
|
+
'[contenteditable]:not([contenteditable="false"])',
|
|
19
|
+
'[role="button"]',
|
|
20
|
+
'[role="link"]',
|
|
21
|
+
'[role="menuitem"]',
|
|
22
|
+
'[role="option"]',
|
|
23
|
+
'[role="switch"]',
|
|
24
|
+
'[role="tab"]',
|
|
25
|
+
].join(', ');
|
|
26
|
+
|
|
27
|
+
/** Adds a touch-only long-press entry point without rendering idle checkboxes. */
|
|
28
|
+
export function useLongPressSelection(onSelect: (id: string) => void) {
|
|
29
|
+
const onSelectRef = useRef(onSelect);
|
|
30
|
+
onSelectRef.current = onSelect;
|
|
31
|
+
const timerRef = useRef<number | null>(null);
|
|
32
|
+
const startRef = useRef<{ x: number; y: number } | null>(null);
|
|
33
|
+
const suppressClickRef = useRef(false);
|
|
34
|
+
|
|
35
|
+
const cancel = useCallback(() => {
|
|
36
|
+
if (timerRef.current !== null) window.clearTimeout(timerRef.current);
|
|
37
|
+
timerRef.current = null;
|
|
38
|
+
startRef.current = null;
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
useEffect(() => cancel, [cancel]);
|
|
42
|
+
|
|
43
|
+
return useCallback((id: string) => ({
|
|
44
|
+
onPointerDown: (event: ReactPointerEvent<HTMLElement>) => {
|
|
45
|
+
if (event.pointerType !== 'touch' || event.button !== 0) return;
|
|
46
|
+
const target = event.target;
|
|
47
|
+
if (
|
|
48
|
+
target instanceof HTMLElement
|
|
49
|
+
&& target !== event.currentTarget
|
|
50
|
+
&& target.closest(INTERACTIVE_SELECTOR)
|
|
51
|
+
) return;
|
|
52
|
+
cancel();
|
|
53
|
+
startRef.current = { x: event.clientX, y: event.clientY };
|
|
54
|
+
timerRef.current = window.setTimeout(() => {
|
|
55
|
+
timerRef.current = null;
|
|
56
|
+
startRef.current = null;
|
|
57
|
+
suppressClickRef.current = true;
|
|
58
|
+
onSelectRef.current(id);
|
|
59
|
+
}, LONG_PRESS_MS);
|
|
60
|
+
},
|
|
61
|
+
onPointerMove: (event: ReactPointerEvent<HTMLElement>) => {
|
|
62
|
+
const start = startRef.current;
|
|
63
|
+
if (!start) return;
|
|
64
|
+
if (
|
|
65
|
+
Math.abs(event.clientX - start.x) > MOVE_TOLERANCE_PX
|
|
66
|
+
|| Math.abs(event.clientY - start.y) > MOVE_TOLERANCE_PX
|
|
67
|
+
) cancel();
|
|
68
|
+
},
|
|
69
|
+
onPointerUp: cancel,
|
|
70
|
+
onPointerCancel: cancel,
|
|
71
|
+
onClickCapture: (event: ReactMouseEvent<HTMLElement>) => {
|
|
72
|
+
if (!suppressClickRef.current) return false;
|
|
73
|
+
suppressClickRef.current = false;
|
|
74
|
+
event.preventDefault();
|
|
75
|
+
event.stopPropagation();
|
|
76
|
+
return true;
|
|
77
|
+
},
|
|
78
|
+
}), [cancel]);
|
|
79
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `useMarqueeSelection`: drag from empty space to select (the rubber band), shared by the Manager
|
|
5
|
+
* (`useCollectionInteractions`) and the toolkit list (`ListReader`), so both run the same code.
|
|
6
|
+
*
|
|
7
|
+
* - A press starts a marquee only on empty space inside the container: not on an item, not on a control
|
|
8
|
+
* (button, link, field). A press on a control does nothing here; a press on an item is left to the item.
|
|
9
|
+
* - The marquee shows once the pointer moved past `threshold` px; the text selection is cleared then.
|
|
10
|
+
* - While dragging, every selectable item the rectangle intersects is selected, live (`onSelect`, in DOM order;
|
|
11
|
+
* the last one is the anchor).
|
|
12
|
+
* - Releasing after a drag swallows the click that follows; a plain click on empty space clears (`onClear`).
|
|
13
|
+
* - Mouse events only: a touch drag scrolls and never draws a marquee.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { useCallback, useEffect, useRef, useState, type MouseEvent as ReactMouseEvent } from 'react';
|
|
17
|
+
|
|
18
|
+
export interface SelectionMarqueeRect {
|
|
19
|
+
left: number;
|
|
20
|
+
top: number;
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Clicks on these belong to the control (Manager `INTERACTIVE_SELECTOR`). */
|
|
26
|
+
export const MARQUEE_INTERACTIVE_SELECTOR =
|
|
27
|
+
'button, a, input, textarea, select, [role="button"], [contenteditable]:not([contenteditable="false"])';
|
|
28
|
+
|
|
29
|
+
export interface UseMarqueeSelectionOptions {
|
|
30
|
+
/** The marquee is on: the collection allows multiple selection and the surface did not opt out. */
|
|
31
|
+
enabled: boolean;
|
|
32
|
+
/** The attribute every item element carries, holding its id. */
|
|
33
|
+
itemAttribute: string;
|
|
34
|
+
/** Items that may join the selection (pending and disabled ones may not). */
|
|
35
|
+
isSelectable: (id: string) => boolean;
|
|
36
|
+
/** The items under the rectangle, in DOM order, on every move of a drag. Replaces the selection. */
|
|
37
|
+
onSelect: (ids: string[]) => void;
|
|
38
|
+
/** A plain click (no drag) on empty space. */
|
|
39
|
+
onClear: () => void;
|
|
40
|
+
/** Distance in px before a press becomes a drag. Default 5. */
|
|
41
|
+
threshold?: number;
|
|
42
|
+
interactiveSelector?: string;
|
|
43
|
+
/** A part of an item a drag may also start from (a row's leading strip), so a list with no empty space left still
|
|
44
|
+
* has a place to start one. A plain click there still reaches the item. Default: empty space only. */
|
|
45
|
+
startSelector?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface MarqueeSelection {
|
|
49
|
+
/** The marquee in viewport coordinates while dragging, else null (draw it with `SelectionMarquee`). */
|
|
50
|
+
rect: SelectionMarqueeRect | null;
|
|
51
|
+
/** Put on the container. */
|
|
52
|
+
onMouseDown: (event: ReactMouseEvent) => void;
|
|
53
|
+
/** Put on the container: swallows the click that ends a drag (or any gesture that armed it). */
|
|
54
|
+
onClickCapture: (event: ReactMouseEvent) => void;
|
|
55
|
+
/** Swallow the next click inside the container (Mod-press toggles use it too). */
|
|
56
|
+
armSuppressNextClick: () => void;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function rectsIntersect(
|
|
60
|
+
a: { left: number; top: number; right: number; bottom: number },
|
|
61
|
+
b: { left: number; top: number; right: number; bottom: number },
|
|
62
|
+
): boolean {
|
|
63
|
+
return a.left < b.right && a.right > b.left && a.top < b.bottom && a.bottom > b.top;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The ids of the selectable items under `rect` inside `container`, in DOM order. */
|
|
67
|
+
export function itemsInMarquee(
|
|
68
|
+
container: ParentNode | null,
|
|
69
|
+
rect: { left: number; top: number; right: number; bottom: number },
|
|
70
|
+
itemAttribute: string,
|
|
71
|
+
isSelectable: (id: string) => boolean,
|
|
72
|
+
): string[] {
|
|
73
|
+
const nodes = container?.querySelectorAll<HTMLElement>(`[${itemAttribute}]`);
|
|
74
|
+
if (!nodes) return [];
|
|
75
|
+
const ids: string[] = [];
|
|
76
|
+
nodes.forEach((node) => {
|
|
77
|
+
if (!rectsIntersect(rect, node.getBoundingClientRect())) return;
|
|
78
|
+
const id = node.getAttribute(itemAttribute);
|
|
79
|
+
if (id && isSelectable(id)) ids.push(id);
|
|
80
|
+
});
|
|
81
|
+
return ids;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function useMarqueeSelection(options: UseMarqueeSelectionOptions): MarqueeSelection {
|
|
85
|
+
const {
|
|
86
|
+
enabled, itemAttribute, threshold = 5, interactiveSelector = MARQUEE_INTERACTIVE_SELECTOR, startSelector,
|
|
87
|
+
} = options;
|
|
88
|
+
const latest = useRef(options);
|
|
89
|
+
latest.current = options;
|
|
90
|
+
const [rect, setRect] = useState<SelectionMarqueeRect | null>(null);
|
|
91
|
+
|
|
92
|
+
const containerRef = useRef<HTMLElement | null>(null);
|
|
93
|
+
const pendingRef = useRef(false);
|
|
94
|
+
const activeRef = useRef(false);
|
|
95
|
+
const startRef = useRef({ x: 0, y: 0 });
|
|
96
|
+
const currentRef = useRef({ x: 0, y: 0 });
|
|
97
|
+
const clickShouldClearRef = useRef(false);
|
|
98
|
+
const suppressNextClickRef = useRef(false);
|
|
99
|
+
|
|
100
|
+
const armSuppressNextClick = useCallback(() => {
|
|
101
|
+
suppressNextClickRef.current = true;
|
|
102
|
+
setTimeout(() => { suppressNextClickRef.current = false; }, 0);
|
|
103
|
+
}, []);
|
|
104
|
+
|
|
105
|
+
const onMouseDown = useCallback((event: ReactMouseEvent) => {
|
|
106
|
+
if (!enabled || event.button !== 0) return;
|
|
107
|
+
const target = event.target as HTMLElement | null;
|
|
108
|
+
if (!target || typeof target.closest !== 'function') return;
|
|
109
|
+
const itemTarget = target.closest(`[${itemAttribute}]`);
|
|
110
|
+
const interactiveTarget = target.closest(interactiveSelector);
|
|
111
|
+
if (interactiveTarget && interactiveTarget !== itemTarget) {
|
|
112
|
+
clickShouldClearRef.current = false;
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
clickShouldClearRef.current = !itemTarget;
|
|
116
|
+
if (itemTarget) {
|
|
117
|
+
const startTarget = startSelector ? target.closest(startSelector) : null;
|
|
118
|
+
if (!startTarget || !itemTarget.contains(startTarget)) return;
|
|
119
|
+
// Neither the item's own drag nor a text selection starts from the strip.
|
|
120
|
+
event.preventDefault();
|
|
121
|
+
}
|
|
122
|
+
containerRef.current = event.currentTarget as HTMLElement;
|
|
123
|
+
pendingRef.current = true;
|
|
124
|
+
startRef.current = { x: event.clientX, y: event.clientY };
|
|
125
|
+
currentRef.current = { x: event.clientX, y: event.clientY };
|
|
126
|
+
}, [enabled, interactiveSelector, itemAttribute, startSelector]);
|
|
127
|
+
|
|
128
|
+
const onClickCapture = useCallback((event: ReactMouseEvent) => {
|
|
129
|
+
if (!suppressNextClickRef.current) return;
|
|
130
|
+
suppressNextClickRef.current = false;
|
|
131
|
+
event.preventDefault();
|
|
132
|
+
event.stopPropagation();
|
|
133
|
+
}, []);
|
|
134
|
+
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
if (!enabled) {
|
|
137
|
+
pendingRef.current = false;
|
|
138
|
+
if (activeRef.current) {
|
|
139
|
+
activeRef.current = false;
|
|
140
|
+
setRect(null);
|
|
141
|
+
}
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
if (typeof document === 'undefined') return undefined;
|
|
145
|
+
const doc = document;
|
|
146
|
+
const handleMouseMove = (event: MouseEvent) => {
|
|
147
|
+
if (pendingRef.current) {
|
|
148
|
+
const dx = event.clientX - startRef.current.x;
|
|
149
|
+
const dy = event.clientY - startRef.current.y;
|
|
150
|
+
if (Math.abs(dx) > threshold || Math.abs(dy) > threshold) {
|
|
151
|
+
pendingRef.current = false;
|
|
152
|
+
activeRef.current = true;
|
|
153
|
+
window.getSelection()?.removeAllRanges();
|
|
154
|
+
} else return;
|
|
155
|
+
}
|
|
156
|
+
if (!activeRef.current) return;
|
|
157
|
+
event.preventDefault();
|
|
158
|
+
currentRef.current = { x: event.clientX, y: event.clientY };
|
|
159
|
+
const next: SelectionMarqueeRect = {
|
|
160
|
+
left: Math.min(startRef.current.x, currentRef.current.x),
|
|
161
|
+
top: Math.min(startRef.current.y, currentRef.current.y),
|
|
162
|
+
width: Math.abs(currentRef.current.x - startRef.current.x),
|
|
163
|
+
height: Math.abs(currentRef.current.y - startRef.current.y),
|
|
164
|
+
};
|
|
165
|
+
setRect(next);
|
|
166
|
+
const { itemAttribute: attribute, isSelectable, onSelect } = latest.current;
|
|
167
|
+
onSelect(itemsInMarquee(containerRef.current, {
|
|
168
|
+
left: next.left, top: next.top, right: next.left + next.width, bottom: next.top + next.height,
|
|
169
|
+
}, attribute, isSelectable));
|
|
170
|
+
};
|
|
171
|
+
const handleMouseUp = () => {
|
|
172
|
+
const wasPending = pendingRef.current;
|
|
173
|
+
const wasActive = activeRef.current;
|
|
174
|
+
pendingRef.current = false;
|
|
175
|
+
activeRef.current = false;
|
|
176
|
+
if (wasActive) {
|
|
177
|
+
setRect(null);
|
|
178
|
+
clickShouldClearRef.current = false;
|
|
179
|
+
armSuppressNextClick();
|
|
180
|
+
} else if (wasPending && clickShouldClearRef.current) {
|
|
181
|
+
clickShouldClearRef.current = false;
|
|
182
|
+
latest.current.onClear();
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
doc.addEventListener('mousemove', handleMouseMove);
|
|
186
|
+
doc.addEventListener('mouseup', handleMouseUp);
|
|
187
|
+
return () => {
|
|
188
|
+
doc.removeEventListener('mousemove', handleMouseMove);
|
|
189
|
+
doc.removeEventListener('mouseup', handleMouseUp);
|
|
190
|
+
};
|
|
191
|
+
}, [armSuppressNextClick, enabled, threshold]);
|
|
192
|
+
|
|
193
|
+
return { rect, onMouseDown, onClickCapture, armSuppressNextClick };
|
|
194
|
+
}
|
|
@@ -43,10 +43,7 @@ export interface MultiSelectController<T> {
|
|
|
43
43
|
onCheckboxClick: (id: string) => (event: ReactMouseEvent) => void;
|
|
44
44
|
onRowMouseDown: (id: string) => (event: ReactMouseEvent) => void;
|
|
45
45
|
getRowProps: (id: string) => { [ROW_ATTR]: string };
|
|
46
|
-
getItemProps:
|
|
47
|
-
[ROW_ATTR]: string;
|
|
48
|
-
onMouseDown: (event: ReactMouseEvent) => void;
|
|
49
|
-
};
|
|
46
|
+
getItemProps: CollectionInteractionController<T>['getItemProps'];
|
|
50
47
|
getCheckboxProps: (id: string) => {
|
|
51
48
|
isSelected: boolean;
|
|
52
49
|
onClick: (event: ReactMouseEvent) => void;
|
|
@@ -72,10 +69,7 @@ export function useMultiSelect<T>(options: UseMultiSelectOptions<T>): MultiSelec
|
|
|
72
69
|
});
|
|
73
70
|
|
|
74
71
|
const getRowProps = useCallback((id: string) => ({ [ROW_ATTR]: id }), []);
|
|
75
|
-
const getItemProps =
|
|
76
|
-
[ROW_ATTR]: id,
|
|
77
|
-
onMouseDown: controller.onRowMouseDown(id),
|
|
78
|
-
}), [controller.onRowMouseDown]);
|
|
72
|
+
const getItemProps = controller.getItemProps;
|
|
79
73
|
|
|
80
74
|
return {
|
|
81
75
|
selectedIds: controller.selectedIds,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
|
4
|
+
import { useNotisRuntime } from '../provider';
|
|
5
|
+
import { useQuery } from './useQuery';
|
|
6
|
+
import { SpaceActionError, spaceQueryKey, type SpaceDocumentBodyRead, type SpaceDocumentBodySave } from '../space';
|
|
7
|
+
|
|
8
|
+
/** Explicit textarea Save. The source owns the draft and retains its request ID
|
|
9
|
+
* until a receipt or unchanged result; background reads never replace that draft. */
|
|
10
|
+
export function useNativeDocumentBody(binding: string, recordKey: string | null, options: {
|
|
11
|
+
readAction: string; writeAction?: string; enabled?: boolean;
|
|
12
|
+
}) {
|
|
13
|
+
const runtime = useNotisRuntime();
|
|
14
|
+
const scope = JSON.stringify(spaceQueryKey(runtime, ['native-body', binding, recordKey, options.readAction, options.writeAction]));
|
|
15
|
+
const currentScope = useRef<string | null>(scope);
|
|
16
|
+
useLayoutEffect(() => {
|
|
17
|
+
currentScope.current = scope;
|
|
18
|
+
return () => { currentScope.current = null; };
|
|
19
|
+
}, [scope]);
|
|
20
|
+
const [state, setState] = useState<{ scope: string; pending: number; error: Error | null }>({ scope, pending: 0, error: null });
|
|
21
|
+
const query = useQuery<SpaceDocumentBodyRead>(['native-body', binding, recordKey, options.readAction], async () => {
|
|
22
|
+
if (!recordKey || runtime?.resource?.kind !== 'space' || !runtime.documentBody) throw new Error('Open a Space document first.');
|
|
23
|
+
const result = await runtime.documentBody({ operation: 'read', binding, recordKey, readAction: options.readAction });
|
|
24
|
+
if (!('content_markdown' in result)) throw new Error('The document response is incomplete.');
|
|
25
|
+
return result;
|
|
26
|
+
}, { readOnly: true, enabled: Boolean(recordKey) && options.enabled !== false });
|
|
27
|
+
const save = useCallback(async (draft: {
|
|
28
|
+
contentMarkdown: string; expectedRevision: number; schemaRevision: number; requestId: string;
|
|
29
|
+
}): Promise<SpaceDocumentBodySave> => {
|
|
30
|
+
if (!recordKey || !options.writeAction || runtime?.resource?.kind !== 'space' || !runtime.documentBody) {
|
|
31
|
+
throw new Error('This Space does not expose a body-edit action.');
|
|
32
|
+
}
|
|
33
|
+
setState(previous => ({ scope, pending: (previous.scope === scope ? previous.pending : 0) + 1, error: null }));
|
|
34
|
+
try {
|
|
35
|
+
const result = await runtime.documentBody({ operation: 'save', binding, recordKey,
|
|
36
|
+
readAction: options.readAction, writeAction: options.writeAction, ...draft });
|
|
37
|
+
if (currentScope.current !== scope) throw new SpaceActionError('This document view changed. Keep the original save request.', draft.requestId);
|
|
38
|
+
if (!('status' in result)) throw new Error('The document save receipt is incomplete.');
|
|
39
|
+
return result;
|
|
40
|
+
} catch (cause) {
|
|
41
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
42
|
+
if (currentScope.current === scope) setState(previous => ({ ...previous, error }));
|
|
43
|
+
throw error;
|
|
44
|
+
} finally {
|
|
45
|
+
if (currentScope.current === scope) setState(previous => ({ ...previous, pending: Math.max(0, previous.pending - 1) }));
|
|
46
|
+
}
|
|
47
|
+
}, [runtime, binding, recordKey, options.readAction, options.writeAction, scope]);
|
|
48
|
+
return { ...query, body: query.data, save, saving: state.scope === scope && state.pending > 0,
|
|
49
|
+
saveError: state.scope === scope ? state.error : null };
|
|
50
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
4
|
+
import { useNotisRuntime } from '../provider';
|
|
5
|
+
import { normalizeDocumentRecord } from '../documents';
|
|
6
|
+
import type { DocumentRecord, NotisRuntime } from '../runtime';
|
|
7
|
+
import type { NativeDelete, NativeInsert, NativeMutationReceipt, NativeQuery, NativeUpdate } from '../nativeData';
|
|
8
|
+
import { useQuery, type UseQueryResult } from './useQuery';
|
|
9
|
+
import { SdkQueryError } from '../queryErrors';
|
|
10
|
+
|
|
11
|
+
function actionFor(runtime: NotisRuntime | null, binding: string, operation: 'query' | 'insert' | 'update' | 'delete'): string {
|
|
12
|
+
const descriptor = runtime?.space?.bindings[binding];
|
|
13
|
+
const action = descriptor?.kind === 'database' ? descriptor.operations?.[operation] : undefined;
|
|
14
|
+
if (runtime?.resource?.kind !== 'space' || !runtime.callAction || !action) {
|
|
15
|
+
const diagnostic = `This Space does not expose ${operation} for database binding "${binding}".`;
|
|
16
|
+
if (operation === 'query') throw new SdkQueryError('bindingUnavailable', diagnostic);
|
|
17
|
+
throw new Error(`This Space does not expose ${operation} for that database binding.`);
|
|
18
|
+
}
|
|
19
|
+
return action;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A declared query adapter, never an account-wide slug or owner lookup.
|
|
24
|
+
*
|
|
25
|
+
* `subscribe: true` follows the binding's live change feed where the host has one (a
|
|
26
|
+
* signed-in Editor of the Space, owner or teammate): `live` is then true and changes made
|
|
27
|
+
* elsewhere (another device, a teammate, an agent, an automation) re-read the rows on their
|
|
28
|
+
* own. Where `live` is false, keep offering a manual reload.
|
|
29
|
+
*/
|
|
30
|
+
export function useNativeDocuments(binding: string, options: {
|
|
31
|
+
query?: NativeQuery; fetchAll?: boolean; enabled?: boolean; staleTimeMs?: number; subscribe?: boolean;
|
|
32
|
+
} = {}): UseQueryResult<{ documents: DocumentRecord[]; schemaRevision: number }> & { documents: DocumentRecord[]; schemaRevision?: number; live: boolean } {
|
|
33
|
+
const runtime = useNotisRuntime();
|
|
34
|
+
const [live, setLive] = useState(false);
|
|
35
|
+
const follow = options.subscribe === true && options.enabled !== false;
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (!follow || !runtime?.subscribeDatabase) { setLive(false); return; }
|
|
38
|
+
let cancelled = false;
|
|
39
|
+
// The Space host re-reads every query of the view when the database changes, so the
|
|
40
|
+
// change callback has nothing left to do; the feed is a signal and never carries rows.
|
|
41
|
+
const stop = runtime.subscribeDatabase(binding, () => {}, { onStatusChange: value => { if (!cancelled) setLive(value); } });
|
|
42
|
+
return () => { cancelled = true; setLive(false); stop?.(); };
|
|
43
|
+
}, [runtime, binding, follow]);
|
|
44
|
+
const locale = runtime?.context?.locale === 'fr' ? 'fr' : 'en';
|
|
45
|
+
const query = useQuery(['native-documents', binding, options.query ?? {}, Boolean(options.fetchAll), locale], async () => {
|
|
46
|
+
const action = actionFor(runtime, binding, 'query');
|
|
47
|
+
if (options.query?.mode && !['rows', 'search'].includes(options.query.mode)) throw new Error('Use useActionQuery for counts and aggregates.');
|
|
48
|
+
const rows: unknown[] = [];
|
|
49
|
+
let offset = options.query?.offset ?? 0;
|
|
50
|
+
let revision: number | undefined;
|
|
51
|
+
while (true) {
|
|
52
|
+
const result = await runtime!.callAction!<{ rows: unknown[]; schema_revision: number; has_more: boolean; next_offset: number | null }>(
|
|
53
|
+
action, { request: { ...options.query, offset } }, { readOnly: true, dedupe: true, schemaRevision: revision });
|
|
54
|
+
if (!result || !Array.isArray(result.rows) || !Number.isSafeInteger(result.schema_revision)) throw new SdkQueryError('responseIncomplete', 'Native query must return rows and a safe integer schema_revision.');
|
|
55
|
+
if (revision !== undefined && revision !== result.schema_revision) throw new SdkQueryError('schemaChanged');
|
|
56
|
+
revision = result.schema_revision;
|
|
57
|
+
rows.push(...result.rows);
|
|
58
|
+
if (!options.fetchAll || !result.has_more) break;
|
|
59
|
+
if (typeof result.next_offset !== 'number' || result.next_offset <= offset) throw new SdkQueryError('responseIncomplete', 'Native query pagination did not advance next_offset.');
|
|
60
|
+
offset = result.next_offset;
|
|
61
|
+
}
|
|
62
|
+
return { documents: rows.map((row) => normalizeDocumentRecord(row, { locale })), schemaRevision: revision! };
|
|
63
|
+
}, { readOnly: true, enabled: options.enabled, staleTimeMs: options.staleTimeMs });
|
|
64
|
+
return { ...query, documents: query.data?.documents ?? EMPTY_DOCUMENTS, schemaRevision: query.data?.schemaRevision, live };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Explicit insert/update/trash; update never silently creates a missing row. */
|
|
68
|
+
export function useNativeMutation<TOperation extends 'insert' | 'update' | 'delete'>(binding: string, operation: TOperation): {
|
|
69
|
+
mutate: (input: TOperation extends 'insert' ? NativeInsert : TOperation extends 'update' ? NativeUpdate : NativeDelete,
|
|
70
|
+
options: { schemaRevision: number; requestId: string }) => Promise<NativeMutationReceipt>;
|
|
71
|
+
loading: boolean; error: Error | null;
|
|
72
|
+
} {
|
|
73
|
+
const runtime = useNotisRuntime();
|
|
74
|
+
const [pending, setPending] = useState(0), [error, setError] = useState<Error | null>(null);
|
|
75
|
+
const mutate = useCallback(async (input: NativeInsert | NativeUpdate | NativeDelete, options: { schemaRevision: number; requestId: string }) => {
|
|
76
|
+
const action = actionFor(runtime, binding, operation);
|
|
77
|
+
setPending((value) => value + 1); setError(null);
|
|
78
|
+
// A trashed row leaves every cached read at once and comes back if the write fails.
|
|
79
|
+
const key = operation === 'delete' && typeof (input as NativeDelete).record_key === 'string' ? (input as NativeDelete).record_key : null;
|
|
80
|
+
const hidden = key ? runtime?.queryClient?.hideRecords?.([key]) : undefined;
|
|
81
|
+
try {
|
|
82
|
+
const receipt = await runtime!.callAction!<NativeMutationReceipt>(action, { request: input }, options);
|
|
83
|
+
hidden?.commit();
|
|
84
|
+
return receipt;
|
|
85
|
+
} catch (cause) {
|
|
86
|
+
hidden?.restore();
|
|
87
|
+
const failure = cause instanceof Error ? cause : new Error(String(cause)); setError(failure); throw failure;
|
|
88
|
+
} finally { setPending((value) => value - 1); }
|
|
89
|
+
}, [runtime, binding, operation]);
|
|
90
|
+
return { mutate, loading: pending > 0, error };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface NativeDeleteManyResult {
|
|
94
|
+
done: NativeDelete[];
|
|
95
|
+
failed: Array<{ input: NativeDelete; error: Error }>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Trashes many rows of one database binding: every row leaves the cached reads at once, the writes run a few at
|
|
100
|
+
* a time (each with its own request ID, `${requestId}:${record_key}`, so a retry with the same `requestId` replays
|
|
101
|
+
* instead of trashing twice), the reads refresh once at the end, and a row whose write failed comes back. It never
|
|
102
|
+
* throws for a failed row: `failed` lists them.
|
|
103
|
+
*/
|
|
104
|
+
export function useNativeDelete(binding: string): {
|
|
105
|
+
deleteMany: (inputs: readonly NativeDelete[], options: { schemaRevision: number; requestId: string }) => Promise<NativeDeleteManyResult>;
|
|
106
|
+
loading: boolean; error: Error | null;
|
|
107
|
+
} {
|
|
108
|
+
const runtime = useNotisRuntime();
|
|
109
|
+
const [pending, setPending] = useState(0), [error, setError] = useState<Error | null>(null);
|
|
110
|
+
const deleteMany = useCallback(async (inputs: readonly NativeDelete[], options: { schemaRevision: number; requestId: string }) => {
|
|
111
|
+
const action = actionFor(runtime, binding, 'delete');
|
|
112
|
+
const result: NativeDeleteManyResult = { done: [], failed: [] };
|
|
113
|
+
if (!inputs.length) return result;
|
|
114
|
+
setPending((value) => value + 1); setError(null);
|
|
115
|
+
const hidden = runtime?.queryClient?.hideRecords?.(inputs.map((input) => input.record_key));
|
|
116
|
+
let next = 0;
|
|
117
|
+
const worker = async () => {
|
|
118
|
+
while (next < inputs.length) {
|
|
119
|
+
const input = inputs[next++];
|
|
120
|
+
try {
|
|
121
|
+
await runtime!.callAction!<NativeMutationReceipt>(action, { request: input },
|
|
122
|
+
{ schemaRevision: options.schemaRevision, requestId: `${options.requestId}:${input.record_key}`, deferRefresh: true });
|
|
123
|
+
result.done.push(input);
|
|
124
|
+
} catch (cause) {
|
|
125
|
+
result.failed.push({ input, error: cause instanceof Error ? cause : new Error(String(cause)) });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
try {
|
|
130
|
+
await Promise.all(Array.from({ length: Math.min(DELETE_CONCURRENCY, inputs.length) }, worker));
|
|
131
|
+
} finally {
|
|
132
|
+
hidden?.commit(result.done.map((input) => input.record_key));
|
|
133
|
+
hidden?.restore(result.failed.map((item) => item.input.record_key));
|
|
134
|
+
runtime?.queryClient?.invalidate();
|
|
135
|
+
if (result.failed.length) setError(result.failed[0].error);
|
|
136
|
+
setPending((value) => value - 1);
|
|
137
|
+
}
|
|
138
|
+
return result;
|
|
139
|
+
}, [runtime, binding]);
|
|
140
|
+
return { deleteMany, loading: pending > 0, error };
|
|
141
|
+
}
|
|
142
|
+
const DELETE_CONCURRENCY = 4;
|
|
143
|
+
const EMPTY_DOCUMENTS: DocumentRecord[] = [];
|