@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
|
@@ -17,22 +17,23 @@ import {
|
|
|
17
17
|
} from '../interactions/actions';
|
|
18
18
|
import {
|
|
19
19
|
activateShortcutCollection,
|
|
20
|
+
releaseShortcutCollection,
|
|
20
21
|
createShortcutCollectionOwner,
|
|
21
22
|
useShortcuts,
|
|
22
23
|
type ShortcutDefinition,
|
|
23
24
|
type ShortcutScope,
|
|
24
25
|
} from '../interactions/shortcuts';
|
|
25
26
|
|
|
27
|
+
import type { MultiSelectActionBarProps } from '../components/MultiSelectActionBar';
|
|
28
|
+
import { isInteractionElementVisible } from '../interactions/visibility';
|
|
29
|
+
import { useLongPressSelection } from './useLongPressSelection';
|
|
30
|
+
import { useMarqueeSelection, type SelectionMarqueeRect } from './useMarqueeSelection';
|
|
31
|
+
|
|
26
32
|
export const COLLECTION_ITEM_ATTRIBUTE = 'data-notis-collection-item-id';
|
|
27
33
|
const LEGACY_ROW_ATTRIBUTE = 'data-notis-row-id';
|
|
28
34
|
const INTERACTIVE_SELECTOR = 'button, a, input, textarea, select, [role="button"], [contenteditable]:not([contenteditable="false"])';
|
|
29
35
|
|
|
30
|
-
export
|
|
31
|
-
left: number;
|
|
32
|
-
top: number;
|
|
33
|
-
width: number;
|
|
34
|
-
height: number;
|
|
35
|
-
}
|
|
36
|
+
export type { SelectionMarqueeRect };
|
|
36
37
|
|
|
37
38
|
export type CollectionInteractionReason =
|
|
38
39
|
| 'activate'
|
|
@@ -93,6 +94,8 @@ export interface UseCollectionInteractionsOptions<T> {
|
|
|
93
94
|
enabled?: boolean;
|
|
94
95
|
bindKeyboardShortcuts?: boolean;
|
|
95
96
|
enableDragSelect?: boolean;
|
|
97
|
+
/** Touch-only long press; optional per view. */
|
|
98
|
+
enableLongPressSelection?: boolean;
|
|
96
99
|
/** Plain item clicks activate the item and clear checkbox selection by default. */
|
|
97
100
|
clearSelectionOnPlainClick?: boolean;
|
|
98
101
|
dragThreshold?: number;
|
|
@@ -114,6 +117,7 @@ export interface CollectionInteractionController<T> {
|
|
|
114
117
|
lastClickedId: string | null;
|
|
115
118
|
dragRect: SelectionMarqueeRect | null;
|
|
116
119
|
actions: ResolvedCollectionAction[];
|
|
120
|
+
getActionBarProps: () => Pick<MultiSelectActionBarProps, 'selectedCount' | 'actions' | 'collectionOwnerId' | 'isAvailable' | 'shortcutsEnabled' | 'onClearSelection'>;
|
|
117
121
|
|
|
118
122
|
isSelected: (id: string) => boolean;
|
|
119
123
|
getSelectedItems: () => T[];
|
|
@@ -128,7 +132,7 @@ export interface CollectionInteractionController<T> {
|
|
|
128
132
|
onCheckboxClick: (id: string) => (event: ReactMouseEvent) => void;
|
|
129
133
|
onRowMouseDown: (id: string) => (event: ReactMouseEvent) => void;
|
|
130
134
|
getRowProps: (id: string) => Record<string, string>;
|
|
131
|
-
getItemProps: (id: string) => {
|
|
135
|
+
getItemProps: (id: string) => Partial<ReturnType<ReturnType<typeof useLongPressSelection>>> & {
|
|
132
136
|
[COLLECTION_ITEM_ATTRIBUTE]: string;
|
|
133
137
|
[LEGACY_ROW_ATTRIBUTE]: string;
|
|
134
138
|
tabIndex: number;
|
|
@@ -185,13 +189,6 @@ function setsEqual(a: ReadonlySet<string>, b: ReadonlySet<string>): boolean {
|
|
|
185
189
|
return true;
|
|
186
190
|
}
|
|
187
191
|
|
|
188
|
-
function rectsIntersect(
|
|
189
|
-
a: { left: number; top: number; right: number; bottom: number },
|
|
190
|
-
b: { left: number; top: number; right: number; bottom: number },
|
|
191
|
-
): boolean {
|
|
192
|
-
return a.left < b.right && a.right > b.left && a.top < b.bottom && a.bottom > b.top;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
192
|
export function useCollectionInteractions<T>(
|
|
196
193
|
options: UseCollectionInteractionsOptions<T>,
|
|
197
194
|
): CollectionInteractionController<T> {
|
|
@@ -213,6 +210,7 @@ export function useCollectionInteractions<T>(
|
|
|
213
210
|
enabled = true,
|
|
214
211
|
bindKeyboardShortcuts = true,
|
|
215
212
|
enableDragSelect = selectionMode === 'multiple',
|
|
213
|
+
enableLongPressSelection = false,
|
|
216
214
|
clearSelectionOnPlainClick = true,
|
|
217
215
|
dragThreshold = 5,
|
|
218
216
|
shortcuts: shortcutOverrides,
|
|
@@ -233,13 +231,14 @@ export function useCollectionInteractions<T>(
|
|
|
233
231
|
defaultAnchorId === undefined ? defaultActiveId : defaultAnchorId,
|
|
234
232
|
);
|
|
235
233
|
const anchorId = controlledAnchorId === undefined ? uncontrolledAnchorId : controlledAnchorId;
|
|
236
|
-
const [dragRect, setDragRect] = useState<SelectionMarqueeRect | null>(null);
|
|
237
234
|
|
|
238
235
|
const itemsRef = useRef(items);
|
|
239
236
|
const getIdRef = useRef(getId);
|
|
240
237
|
const selectedIdsRef = useRef(selectedIds);
|
|
241
238
|
const activeIdRef = useRef(activeId);
|
|
242
239
|
const anchorIdRef = useRef(anchorId);
|
|
240
|
+
// Keep manually selected rows outside the current keyboard range on reversal.
|
|
241
|
+
const keyboardRangeBaseRef = useRef<Set<string> | null>(null);
|
|
243
242
|
const onSelectionChangeRef = useRef(onSelectionChange);
|
|
244
243
|
const onActiveIdChangeRef = useRef(onActiveIdChange);
|
|
245
244
|
const onAnchorIdChangeRef = useRef(onAnchorIdChange);
|
|
@@ -247,6 +246,11 @@ export function useCollectionInteractions<T>(
|
|
|
247
246
|
const onActivateRef = useRef(onActivate);
|
|
248
247
|
const resolveNextIdRef = useRef(resolveNextId);
|
|
249
248
|
const containerRef = useRef<HTMLElement | null>(null);
|
|
249
|
+
const [containerElement, setContainerElement] = useState<HTMLElement | null>(null);
|
|
250
|
+
const setContainerRef = useCallback((node: HTMLElement | null) => {
|
|
251
|
+
containerRef.current = node;
|
|
252
|
+
setContainerElement(node);
|
|
253
|
+
}, []);
|
|
250
254
|
const shortcutCollectionOwnerRef = useRef<string | null>(null);
|
|
251
255
|
if (!shortcutCollectionOwnerRef.current) {
|
|
252
256
|
shortcutCollectionOwnerRef.current = createShortcutCollectionOwner();
|
|
@@ -254,6 +258,9 @@ export function useCollectionInteractions<T>(
|
|
|
254
258
|
|
|
255
259
|
itemsRef.current = items;
|
|
256
260
|
getIdRef.current = getId;
|
|
261
|
+
if (!setsEqual(selectedIdsRef.current, selectedIds) || anchorIdRef.current !== anchorId) {
|
|
262
|
+
keyboardRangeBaseRef.current = null;
|
|
263
|
+
}
|
|
257
264
|
selectedIdsRef.current = selectedIds;
|
|
258
265
|
activeIdRef.current = activeId;
|
|
259
266
|
anchorIdRef.current = anchorId;
|
|
@@ -316,6 +323,7 @@ export function useCollectionInteractions<T>(
|
|
|
316
323
|
const activate = useCallback((id: string) => {
|
|
317
324
|
const item = itemsRef.current.find((candidate) => getIdRef.current(candidate) === id);
|
|
318
325
|
if (!item || !selectableIdsRef.current.includes(id)) return;
|
|
326
|
+
keyboardRangeBaseRef.current = null;
|
|
319
327
|
setActive(id, 'activate');
|
|
320
328
|
setAnchor(id, 'activate');
|
|
321
329
|
onActivateRef.current?.(item, id);
|
|
@@ -323,6 +331,7 @@ export function useCollectionInteractions<T>(
|
|
|
323
331
|
|
|
324
332
|
const toggle = useCallback((id: string) => {
|
|
325
333
|
if (selectionMode === 'none' || !selectableIdsRef.current.includes(id)) return;
|
|
334
|
+
keyboardRangeBaseRef.current = null;
|
|
326
335
|
const next = new Set(selectedIdsRef.current);
|
|
327
336
|
if (next.has(id)) next.delete(id);
|
|
328
337
|
else {
|
|
@@ -334,6 +343,7 @@ export function useCollectionInteractions<T>(
|
|
|
334
343
|
}, [commitSelection, selectionMode, setAnchor]);
|
|
335
344
|
|
|
336
345
|
const select = useCallback((ids: string[]) => {
|
|
346
|
+
keyboardRangeBaseRef.current = null;
|
|
337
347
|
const next = new Set(ids);
|
|
338
348
|
const last = ids[ids.length - 1] ?? null;
|
|
339
349
|
setAnchor(last, 'toggle');
|
|
@@ -341,12 +351,14 @@ export function useCollectionInteractions<T>(
|
|
|
341
351
|
}, [commitSelection, setAnchor]);
|
|
342
352
|
|
|
343
353
|
const clear = useCallback(() => {
|
|
354
|
+
keyboardRangeBaseRef.current = null;
|
|
344
355
|
setAnchor(null, 'clear');
|
|
345
356
|
commitSelection(new Set(), 'clear');
|
|
346
357
|
}, [commitSelection, setAnchor]);
|
|
347
358
|
|
|
348
359
|
const selectAll = useCallback(() => {
|
|
349
360
|
if (selectionMode === 'none') return;
|
|
361
|
+
keyboardRangeBaseRef.current = null;
|
|
350
362
|
const ids = selectionMode === 'single' ? selectableIdsRef.current.slice(0, 1) : selectableIdsRef.current;
|
|
351
363
|
const last = ids[ids.length - 1] ?? null;
|
|
352
364
|
setAnchor(last, 'select-all');
|
|
@@ -361,12 +373,16 @@ export function useCollectionInteractions<T>(
|
|
|
361
373
|
commitSelection(new Set([toId]), reason);
|
|
362
374
|
return;
|
|
363
375
|
}
|
|
364
|
-
commitSelection(new Set(
|
|
376
|
+
commitSelection(new Set([
|
|
377
|
+
...(keyboardRangeBaseRef.current ?? []),
|
|
378
|
+
...ids.slice(Math.min(from, to), Math.max(from, to) + 1),
|
|
379
|
+
]), reason);
|
|
365
380
|
}, [commitSelection]);
|
|
366
381
|
|
|
367
382
|
const selectRange = useCallback((fromId: string, toId: string) => {
|
|
368
383
|
const ids = selectableIdsRef.current;
|
|
369
384
|
if (selectionMode === 'none' || !ids.includes(toId)) return;
|
|
385
|
+
keyboardRangeBaseRef.current = null;
|
|
370
386
|
const resolvedFromId = ids.includes(fromId)
|
|
371
387
|
? fromId
|
|
372
388
|
: activeIdRef.current && ids.includes(activeIdRef.current)
|
|
@@ -390,6 +406,9 @@ export function useCollectionInteractions<T>(
|
|
|
390
406
|
|
|
391
407
|
useEffect(() => {
|
|
392
408
|
const allowed = new Set(selectableIds);
|
|
409
|
+
if (keyboardRangeBaseRef.current) {
|
|
410
|
+
keyboardRangeBaseRef.current = new Set(Array.from(keyboardRangeBaseRef.current).filter((id) => allowed.has(id)));
|
|
411
|
+
}
|
|
393
412
|
const pruned = new Set(Array.from(selectedIdsRef.current).filter((id) => allowed.has(id)));
|
|
394
413
|
if (!setsEqual(pruned, selectedIdsRef.current)) commitSelection(pruned, 'items-changed');
|
|
395
414
|
if (activeIdRef.current && !allowed.has(activeIdRef.current)) setActive(null, 'items-changed');
|
|
@@ -398,11 +417,39 @@ export function useCollectionInteractions<T>(
|
|
|
398
417
|
}
|
|
399
418
|
}, [commitSelection, selectableIds, setActive, setAnchor]);
|
|
400
419
|
|
|
420
|
+
const focusItem = useCallback((id: string) => {
|
|
421
|
+
const nodes = containerRef.current?.querySelectorAll<HTMLElement>(`[${COLLECTION_ITEM_ATTRIBUTE}]`);
|
|
422
|
+
const node = nodes ? Array.from(nodes).find(
|
|
423
|
+
(candidate) => candidate.getAttribute(COLLECTION_ITEM_ATTRIBUTE) === id && isInteractionElementVisible(candidate),
|
|
424
|
+
) : null;
|
|
425
|
+
node?.focus({ preventScroll: true });
|
|
426
|
+
node?.scrollIntoView?.({ block: 'nearest', inline: 'nearest' });
|
|
427
|
+
}, []);
|
|
428
|
+
|
|
429
|
+
const focusPointerItem = useCallback((id: string) => {
|
|
430
|
+
setActive(id, 'activate');
|
|
431
|
+
focusItem(id);
|
|
432
|
+
}, [focusItem, setActive]);
|
|
433
|
+
|
|
434
|
+
const selectPointerRange = useCallback((id: string) => {
|
|
435
|
+
const rangeAnchor = anchorIdRef.current ?? activeIdRef.current ?? id;
|
|
436
|
+
const ids = selectableIdsRef.current;
|
|
437
|
+
const from = ids.indexOf(rangeAnchor);
|
|
438
|
+
const to = ids.indexOf(id);
|
|
439
|
+
const range = new Set(ids.slice(Math.min(from, to), Math.max(from, to) + 1));
|
|
440
|
+
const base = keyboardRangeBaseRef.current ?? new Set(Array.from(selectedIdsRef.current).filter((selected) => !range.has(selected)));
|
|
441
|
+
keyboardRangeBaseRef.current = base;
|
|
442
|
+
setAnchor(rangeAnchor, 'range');
|
|
443
|
+
replaceRange(rangeAnchor, id, 'range');
|
|
444
|
+
focusPointerItem(id);
|
|
445
|
+
}, [focusPointerItem, replaceRange, setAnchor]);
|
|
446
|
+
|
|
401
447
|
const moveActive = useCallback((direction: CollectionNavigationDirection, extend = false) => {
|
|
402
448
|
const ids = selectableIdsRef.current;
|
|
403
449
|
if (ids.length === 0) return;
|
|
404
450
|
const current = activeIdRef.current;
|
|
405
|
-
|
|
451
|
+
// Range gestures follow the displayed order, independent of asymmetric grid geometry.
|
|
452
|
+
let next = extend ? null : resolveNextIdRef.current?.({
|
|
406
453
|
direction,
|
|
407
454
|
currentId: current,
|
|
408
455
|
items: itemsRef.current,
|
|
@@ -419,23 +466,21 @@ export function useCollectionInteractions<T>(
|
|
|
419
466
|
if (!next || next === current) return;
|
|
420
467
|
const stableAnchor = anchorIdRef.current ?? current ?? next;
|
|
421
468
|
if (extend) {
|
|
469
|
+
keyboardRangeBaseRef.current ??= new Set(
|
|
470
|
+
Array.from(selectedIdsRef.current).filter((id) => id !== stableAnchor),
|
|
471
|
+
);
|
|
422
472
|
if (!anchorIdRef.current) {
|
|
423
473
|
setAnchor(stableAnchor, 'range');
|
|
424
474
|
}
|
|
425
475
|
replaceRange(stableAnchor, next, 'range');
|
|
426
476
|
} else {
|
|
477
|
+
keyboardRangeBaseRef.current = new Set();
|
|
427
478
|
setAnchor(next, 'navigate');
|
|
428
479
|
}
|
|
429
480
|
setActive(next, extend ? 'range' : 'navigate');
|
|
430
481
|
const nextId = next;
|
|
431
|
-
requestAnimationFrame(() =>
|
|
432
|
-
|
|
433
|
-
const node = nodes ? Array.from(nodes).find(
|
|
434
|
-
(candidate) => candidate.getAttribute(COLLECTION_ITEM_ATTRIBUTE) === nextId,
|
|
435
|
-
) : null;
|
|
436
|
-
node?.focus({ preventScroll: true });
|
|
437
|
-
});
|
|
438
|
-
}, [replaceRange, setActive, setAnchor]);
|
|
482
|
+
requestAnimationFrame(() => focusItem(nextId));
|
|
483
|
+
}, [focusItem, replaceRange, setActive, setAnchor]);
|
|
439
484
|
|
|
440
485
|
const keyboard = useMemo<CollectionKeyboardShortcuts>(
|
|
441
486
|
() => ({ ...DEFAULT_SHORTCUTS, ...(shortcutOverrides || {}) }),
|
|
@@ -456,7 +501,9 @@ export function useCollectionInteractions<T>(
|
|
|
456
501
|
const extendDown = filterShortcutKeys(keyboard.extendNext, (key) => !key.toLowerCase().endsWith('arrowright'));
|
|
457
502
|
const extendLeft = filterShortcutKeys(keyboard.extendPrevious, (key) => key.toLowerCase().endsWith('arrowleft'));
|
|
458
503
|
const extendUp = filterShortcutKeys(keyboard.extendPrevious, (key) => !key.toLowerCase().endsWith('arrowleft'));
|
|
459
|
-
|
|
504
|
+
// An empty collection has nothing to dismiss. Let the enclosing surface
|
|
505
|
+
// own Escape (for example Desktop's final Inbox navigation layer).
|
|
506
|
+
add('collection.clear', 'Clear selection', selectedIds.size > 0 ? keyboard.clear : false, clear);
|
|
460
507
|
add('collection.select-all', 'Select all visible items', selectionMode === 'none' ? false : keyboard.selectAll, selectAll);
|
|
461
508
|
add('collection.toggle', 'Toggle active item', selectionMode === 'none' ? false : keyboard.toggle, () => {
|
|
462
509
|
const id = activeIdRef.current ?? anchorIdRef.current;
|
|
@@ -477,124 +524,52 @@ export function useCollectionInteractions<T>(
|
|
|
477
524
|
if (id) activate(id);
|
|
478
525
|
});
|
|
479
526
|
return definitions;
|
|
480
|
-
}, [activate, clear, keyboard, moveActive, selectAll, selectionMode, toggle]);
|
|
527
|
+
}, [activate, clear, keyboard, moveActive, selectAll, selectedIds.size, selectionMode, toggle]);
|
|
528
|
+
const isAvailable = useCallback(() => isInteractionElementVisible(containerRef.current), []);
|
|
529
|
+
useEffect(() => {
|
|
530
|
+
const owner = shortcutCollectionOwnerRef.current!;
|
|
531
|
+
const container = containerElement;
|
|
532
|
+
const Observer = container?.ownerDocument.defaultView?.MutationObserver;
|
|
533
|
+
const observer = Observer ? new Observer(() => {
|
|
534
|
+
if (!isAvailable()) releaseShortcutCollection(owner);
|
|
535
|
+
}) : null;
|
|
536
|
+
let element: Element | null = container;
|
|
537
|
+
while (element) {
|
|
538
|
+
observer?.observe(element, { attributes: true, attributeFilter: ['hidden', 'inert', 'aria-hidden', 'style', 'class'] });
|
|
539
|
+
const root: Node = element.getRootNode();
|
|
540
|
+
element = element.parentElement ?? ('host' in root ? (root as ShadowRoot).host : null);
|
|
541
|
+
}
|
|
542
|
+
return () => { observer?.disconnect(); releaseShortcutCollection(owner); };
|
|
543
|
+
}, [containerElement, isAvailable]);
|
|
544
|
+
|
|
481
545
|
useShortcuts(keyDefinitions, {
|
|
482
546
|
enabled: enabled && bindKeyboardShortcuts,
|
|
483
547
|
scope: shortcutScope,
|
|
484
548
|
priority: shortcutPriority,
|
|
485
549
|
collectionOwnerId: shortcutCollectionOwnerRef.current,
|
|
550
|
+
isAvailable,
|
|
486
551
|
});
|
|
487
552
|
|
|
488
553
|
const activateCollectionShortcuts = useCallback(() => {
|
|
489
554
|
activateShortcutCollection(shortcutCollectionOwnerRef.current!);
|
|
490
555
|
}, []);
|
|
491
556
|
|
|
492
|
-
|
|
493
|
-
const
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
const armSuppressNextClick = useCallback(() => {
|
|
500
|
-
suppressNextClickRef.current = true;
|
|
501
|
-
setTimeout(() => { suppressNextClickRef.current = false; }, 0);
|
|
502
|
-
}, []);
|
|
503
|
-
|
|
504
|
-
const computeDragRect = useCallback((): SelectionMarqueeRect => ({
|
|
505
|
-
left: Math.min(dragStartRef.current.x, dragCurrentRef.current.x),
|
|
506
|
-
top: Math.min(dragStartRef.current.y, dragCurrentRef.current.y),
|
|
507
|
-
width: Math.abs(dragCurrentRef.current.x - dragStartRef.current.x),
|
|
508
|
-
height: Math.abs(dragCurrentRef.current.y - dragStartRef.current.y),
|
|
509
|
-
}), []);
|
|
510
|
-
|
|
511
|
-
const computeIntersectingIds = useCallback((rect: {
|
|
512
|
-
left: number; top: number; right: number; bottom: number;
|
|
513
|
-
}) => {
|
|
514
|
-
const nodes = containerRef.current?.querySelectorAll<HTMLElement>(`[${COLLECTION_ITEM_ATTRIBUTE}]`);
|
|
515
|
-
if (!nodes) return [];
|
|
516
|
-
const ids: string[] = [];
|
|
517
|
-
nodes.forEach((node) => {
|
|
518
|
-
if (!rectsIntersect(rect, node.getBoundingClientRect())) return;
|
|
519
|
-
const id = node.getAttribute(COLLECTION_ITEM_ATTRIBUTE);
|
|
520
|
-
if (id && selectableIdsRef.current.includes(id)) ids.push(id);
|
|
521
|
-
});
|
|
522
|
-
return ids;
|
|
523
|
-
}, []);
|
|
524
|
-
|
|
525
|
-
const handleContainerMouseDown = useCallback((event: ReactMouseEvent) => {
|
|
526
|
-
if (!enabled || event.button !== 0) return;
|
|
527
|
-
const target = event.target as HTMLElement | null;
|
|
528
|
-
if (!target) return;
|
|
529
|
-
const itemTarget = target.closest(`[${COLLECTION_ITEM_ATTRIBUTE}]`);
|
|
530
|
-
const interactiveTarget = target.closest(INTERACTIVE_SELECTOR);
|
|
531
|
-
if (interactiveTarget && interactiveTarget !== itemTarget) {
|
|
532
|
-
clickShouldClearRef.current = false;
|
|
533
|
-
return;
|
|
534
|
-
}
|
|
535
|
-
clickShouldClearRef.current = !itemTarget;
|
|
536
|
-
if (itemTarget) return;
|
|
537
|
-
if (!enableDragSelect || selectionMode !== 'multiple') return;
|
|
538
|
-
dragPendingRef.current = true;
|
|
539
|
-
dragStartRef.current = { x: event.clientX, y: event.clientY };
|
|
540
|
-
dragCurrentRef.current = { x: event.clientX, y: event.clientY };
|
|
541
|
-
}, [enableDragSelect, enabled, selectionMode]);
|
|
542
|
-
|
|
543
|
-
const handleContainerClickCapture = useCallback((event: ReactMouseEvent) => {
|
|
544
|
-
if (!suppressNextClickRef.current) return;
|
|
545
|
-
suppressNextClickRef.current = false;
|
|
546
|
-
event.preventDefault();
|
|
547
|
-
event.stopPropagation();
|
|
548
|
-
}, []);
|
|
549
|
-
|
|
550
|
-
useEffect(() => {
|
|
551
|
-
if (!enabled || !enableDragSelect || selectionMode !== 'multiple') return;
|
|
552
|
-
const handleMouseMove = (event: MouseEvent) => {
|
|
553
|
-
if (dragPendingRef.current) {
|
|
554
|
-
const dx = event.clientX - dragStartRef.current.x;
|
|
555
|
-
const dy = event.clientY - dragStartRef.current.y;
|
|
556
|
-
if (Math.abs(dx) > dragThreshold || Math.abs(dy) > dragThreshold) {
|
|
557
|
-
dragPendingRef.current = false;
|
|
558
|
-
dragActiveRef.current = true;
|
|
559
|
-
window.getSelection()?.removeAllRanges();
|
|
560
|
-
} else return;
|
|
561
|
-
}
|
|
562
|
-
if (!dragActiveRef.current) return;
|
|
563
|
-
event.preventDefault();
|
|
564
|
-
dragCurrentRef.current = { x: event.clientX, y: event.clientY };
|
|
565
|
-
const rect = computeDragRect();
|
|
566
|
-
setDragRect(rect);
|
|
567
|
-
const ids = computeIntersectingIds({
|
|
568
|
-
left: rect.left,
|
|
569
|
-
top: rect.top,
|
|
570
|
-
right: rect.left + rect.width,
|
|
571
|
-
bottom: rect.top + rect.height,
|
|
572
|
-
});
|
|
557
|
+
// The marquee (drag from empty space) is the shared one the toolkit list uses too.
|
|
558
|
+
const marquee = useMarqueeSelection({
|
|
559
|
+
enabled: enabled && enableDragSelect && selectionMode === 'multiple',
|
|
560
|
+
itemAttribute: COLLECTION_ITEM_ATTRIBUTE,
|
|
561
|
+
isSelectable: (id) => selectableIdsRef.current.includes(id),
|
|
562
|
+
onSelect: (ids) => {
|
|
573
563
|
commitSelection(new Set(ids), 'drag');
|
|
574
564
|
const last = ids[ids.length - 1] ?? null;
|
|
575
565
|
setAnchor(last, 'drag');
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
setDragRect(null);
|
|
584
|
-
clickShouldClearRef.current = false;
|
|
585
|
-
armSuppressNextClick();
|
|
586
|
-
} else if (wasPending && clickShouldClearRef.current) {
|
|
587
|
-
clickShouldClearRef.current = false;
|
|
588
|
-
clear();
|
|
589
|
-
}
|
|
590
|
-
};
|
|
591
|
-
document.addEventListener('mousemove', handleMouseMove);
|
|
592
|
-
document.addEventListener('mouseup', handleMouseUp);
|
|
593
|
-
return () => {
|
|
594
|
-
document.removeEventListener('mousemove', handleMouseMove);
|
|
595
|
-
document.removeEventListener('mouseup', handleMouseUp);
|
|
596
|
-
};
|
|
597
|
-
}, [armSuppressNextClick, clear, commitSelection, computeDragRect, computeIntersectingIds, dragThreshold, enableDragSelect, enabled, selectionMode, setAnchor]);
|
|
566
|
+
},
|
|
567
|
+
onClear: clear,
|
|
568
|
+
threshold: dragThreshold,
|
|
569
|
+
interactiveSelector: INTERACTIVE_SELECTOR,
|
|
570
|
+
});
|
|
571
|
+
const dragRect = marquee.rect;
|
|
572
|
+
const { armSuppressNextClick, onMouseDown: handleContainerMouseDown, onClickCapture: handleContainerClickCapture } = marquee;
|
|
598
573
|
|
|
599
574
|
const onRowMouseDown = useCallback((id: string) => (event: ReactMouseEvent) => {
|
|
600
575
|
if (!enabled || event.button !== 0 || event.shiftKey || !(event.metaKey || event.ctrlKey)) return;
|
|
@@ -604,23 +579,34 @@ export function useCollectionInteractions<T>(
|
|
|
604
579
|
event.preventDefault();
|
|
605
580
|
event.stopPropagation();
|
|
606
581
|
toggle(id);
|
|
582
|
+
focusPointerItem(id);
|
|
607
583
|
armSuppressNextClick();
|
|
608
|
-
}, [armSuppressNextClick, enabled, toggle]);
|
|
584
|
+
}, [armSuppressNextClick, enabled, focusPointerItem, toggle]);
|
|
609
585
|
|
|
610
586
|
const onCheckboxClick = useCallback((id: string) => (event: ReactMouseEvent) => {
|
|
611
587
|
event.stopPropagation();
|
|
588
|
+
if (!enabled) return;
|
|
612
589
|
if (event.shiftKey && selectionMode === 'multiple') {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
590
|
+
selectPointerRange(id);
|
|
591
|
+
} else {
|
|
592
|
+
toggle(id);
|
|
593
|
+
focusPointerItem(id);
|
|
594
|
+
}
|
|
595
|
+
}, [enabled, focusPointerItem, selectPointerRange, selectionMode, toggle]);
|
|
617
596
|
|
|
618
597
|
const getRowProps = useCallback((id: string) => ({
|
|
619
598
|
[COLLECTION_ITEM_ATTRIBUTE]: id,
|
|
620
599
|
[LEGACY_ROW_ATTRIBUTE]: id,
|
|
621
600
|
}), []);
|
|
622
601
|
|
|
602
|
+
const longPress = useLongPressSelection((id) => {
|
|
603
|
+
if (!enabled || selectionMode === 'none') return;
|
|
604
|
+
activateCollectionShortcuts();
|
|
605
|
+
toggle(id);
|
|
606
|
+
focusPointerItem(id);
|
|
607
|
+
});
|
|
623
608
|
const getItemProps = useCallback((id: string) => ({
|
|
609
|
+
...(enableLongPressSelection ? longPress(id) : {}),
|
|
624
610
|
[COLLECTION_ITEM_ATTRIBUTE]: id,
|
|
625
611
|
[LEGACY_ROW_ATTRIBUTE]: id,
|
|
626
612
|
tabIndex: activeIdRef.current === id || (!activeIdRef.current && selectableIdsRef.current[0] === id) ? 0 : -1,
|
|
@@ -633,15 +619,14 @@ export function useCollectionInteractions<T>(
|
|
|
633
619
|
},
|
|
634
620
|
onMouseDown: onRowMouseDown(id),
|
|
635
621
|
onClick: (event: ReactMouseEvent) => {
|
|
636
|
-
if (event.defaultPrevented) return;
|
|
622
|
+
if (!enabled || event.defaultPrevented) return;
|
|
637
623
|
if (!selectableIdsRef.current.includes(id)) return;
|
|
638
624
|
const target = event.target as HTMLElement | null;
|
|
639
625
|
const interactiveTarget = target?.closest(INTERACTIVE_SELECTOR);
|
|
640
626
|
if (interactiveTarget && interactiveTarget !== event.currentTarget) return;
|
|
641
627
|
if (event.shiftKey && selectionMode === 'multiple') {
|
|
642
628
|
event.preventDefault();
|
|
643
|
-
|
|
644
|
-
selectRange(rangeAnchor, id);
|
|
629
|
+
selectPointerRange(id);
|
|
645
630
|
return;
|
|
646
631
|
}
|
|
647
632
|
if (event.metaKey || event.ctrlKey) return;
|
|
@@ -649,19 +634,19 @@ export function useCollectionInteractions<T>(
|
|
|
649
634
|
activate(id);
|
|
650
635
|
},
|
|
651
636
|
onKeyDown: (event: ReactKeyboardEvent) => {
|
|
652
|
-
if (event.key !== ' ' || event.metaKey || event.ctrlKey || event.altKey) return;
|
|
637
|
+
if (!enabled || event.key !== ' ' || event.metaKey || event.ctrlKey || event.altKey) return;
|
|
653
638
|
if (event.target !== event.currentTarget) return;
|
|
654
639
|
event.preventDefault();
|
|
655
640
|
if (selectionMode === 'none') activate(id);
|
|
656
641
|
else toggle(id);
|
|
657
642
|
},
|
|
658
|
-
}), [activate, activateCollectionShortcuts, clear, clearSelectionOnPlainClick, onRowMouseDown,
|
|
643
|
+
}), [activate, activateCollectionShortcuts, clear, clearSelectionOnPlainClick, enabled, enableLongPressSelection, longPress, onRowMouseDown, selectPointerRange, selectionMode, setActive, toggle]);
|
|
659
644
|
|
|
660
645
|
const getCheckboxProps = useCallback((id: string) => ({
|
|
661
646
|
isSelected: selectedIdsRef.current.has(id),
|
|
662
|
-
disabled: !selectableIdsRef.current.includes(id),
|
|
647
|
+
disabled: !enabled || !selectableIdsRef.current.includes(id),
|
|
663
648
|
onClick: onCheckboxClick(id),
|
|
664
|
-
}), [onCheckboxClick]);
|
|
649
|
+
}), [enabled, onCheckboxClick]);
|
|
665
650
|
|
|
666
651
|
const getSelectedItems = useCallback(() => getItemsForIds(selectedIdsRef.current), [getItemsForIds]);
|
|
667
652
|
const isSelected = useCallback((id: string) => selectedIds.has(id), [selectedIds]);
|
|
@@ -681,14 +666,38 @@ export function useCollectionInteractions<T>(
|
|
|
681
666
|
icon: action.icon,
|
|
682
667
|
shortcut,
|
|
683
668
|
destructive: action.destructive ?? action.intent === 'delete',
|
|
684
|
-
disabled,
|
|
669
|
+
disabled: !enabled || disabled,
|
|
685
670
|
pending: action.pending,
|
|
686
|
-
onRun: () => action.onRun(actionContext),
|
|
671
|
+
onRun: () => { if (!enabled || disabled || action.pending) return; return action.onRun(actionContext); },
|
|
687
672
|
};
|
|
688
|
-
}), [actionContext, actionDefinitions]);
|
|
673
|
+
}), [actionContext, actionDefinitions, enabled]);
|
|
674
|
+
|
|
675
|
+
// Optimistic filtering can remove the last selected row before a request settles.
|
|
676
|
+
// Keep pending keys on the visible controller, independently of toolbar mounting.
|
|
677
|
+
useShortcuts(resolvedActions.flatMap((action): ShortcutDefinition[] => (
|
|
678
|
+
action.shortcut && (action.pending || action.disabled)
|
|
679
|
+
? [{ id: `collection.pending.${action.id}`, keys: action.shortcut, allowRepeat: true, onTrigger: () => undefined }]
|
|
680
|
+
: []
|
|
681
|
+
)), {
|
|
682
|
+
enabled: bindKeyboardShortcuts && resolvedActions.some((action) => action.pending),
|
|
683
|
+
scope: shortcutScope,
|
|
684
|
+
priority: shortcutPriority + 25,
|
|
685
|
+
collectionOwnerId: shortcutCollectionOwnerRef.current!,
|
|
686
|
+
isAvailable,
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
const getActionBarProps = useCallback(() => ({
|
|
690
|
+
selectedCount: selectedIds.size,
|
|
691
|
+
actions: resolvedActions,
|
|
692
|
+
collectionOwnerId: shortcutCollectionOwnerRef.current!,
|
|
693
|
+
isAvailable,
|
|
694
|
+
// Pending work may disable collection gestures but must retain its action keys.
|
|
695
|
+
shortcutsEnabled: bindKeyboardShortcuts && (enabled || resolvedActions.some((action) => action.pending)),
|
|
696
|
+
onClearSelection: enabled ? clear : undefined,
|
|
697
|
+
}), [selectedIds.size, resolvedActions, isAvailable, enabled, bindKeyboardShortcuts, clear]);
|
|
689
698
|
|
|
690
699
|
const getContainerProps = useCallback(() => ({
|
|
691
|
-
ref:
|
|
700
|
+
ref: setContainerRef,
|
|
692
701
|
onMouseDown: (event: ReactMouseEvent) => {
|
|
693
702
|
activateCollectionShortcuts();
|
|
694
703
|
handleContainerMouseDown(event);
|
|
@@ -696,7 +705,7 @@ export function useCollectionInteractions<T>(
|
|
|
696
705
|
onFocusCapture: activateCollectionShortcuts,
|
|
697
706
|
onClickCapture: handleContainerClickCapture,
|
|
698
707
|
style: { userSelect: 'none' as const },
|
|
699
|
-
}), [activateCollectionShortcuts, handleContainerClickCapture, handleContainerMouseDown]);
|
|
708
|
+
}), [activateCollectionShortcuts, handleContainerClickCapture, handleContainerMouseDown, setContainerRef]);
|
|
700
709
|
|
|
701
710
|
return {
|
|
702
711
|
selectedIds,
|
|
@@ -707,6 +716,7 @@ export function useCollectionInteractions<T>(
|
|
|
707
716
|
lastClickedId: anchorId,
|
|
708
717
|
dragRect,
|
|
709
718
|
actions: resolvedActions,
|
|
719
|
+
getActionBarProps,
|
|
710
720
|
isSelected,
|
|
711
721
|
getSelectedItems,
|
|
712
722
|
setActive,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useQuery } from './useQuery';
|
|
4
|
+
import { useNotisRuntime } from '../provider';
|
|
5
|
+
import { normalizeDatabaseProperty, optionalString } from '../documents';
|
|
6
|
+
import type { DatabaseProperty } from '../runtime';
|
|
7
|
+
|
|
8
|
+
interface GetDatabaseResult {
|
|
9
|
+
database?: {
|
|
10
|
+
name?: string;
|
|
11
|
+
description?: string | null;
|
|
12
|
+
schema?: {
|
|
13
|
+
properties?: unknown[];
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
message?: string;
|
|
17
|
+
error?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface UseDatabaseSchemaResult {
|
|
21
|
+
name: string | null;
|
|
22
|
+
description: string | null;
|
|
23
|
+
properties: DatabaseProperty[];
|
|
24
|
+
loading: boolean;
|
|
25
|
+
isFetching: boolean;
|
|
26
|
+
hasData: boolean;
|
|
27
|
+
error: Error | null;
|
|
28
|
+
/** Reads again; resolves to the read's error, or null when it succeeded. */
|
|
29
|
+
refetch: () => Promise<Error | null>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Fetch a database's schema (normalized properties + metadata). */
|
|
33
|
+
export function useDatabaseSchema(databaseSlug: string): UseDatabaseSchemaResult {
|
|
34
|
+
const runtime = useNotisRuntime();
|
|
35
|
+
const query = useQuery(['database-schema', databaseSlug], async () => {
|
|
36
|
+
const result = await runtime!.callTool<GetDatabaseResult>('LOCAL_NOTIS_DATABASE_GET_DATABASE', {
|
|
37
|
+
database_slug: databaseSlug,
|
|
38
|
+
}, { dedupe: true, readOnly: true });
|
|
39
|
+
if (!result.database) throw new Error(result.error || result.message || 'Database not found');
|
|
40
|
+
return {
|
|
41
|
+
name: optionalString(result.database.name),
|
|
42
|
+
description: optionalString(result.database.description),
|
|
43
|
+
properties: (result.database.schema?.properties ?? []).map(normalizeDatabaseProperty)
|
|
44
|
+
.filter((property): property is DatabaseProperty => Boolean(property)),
|
|
45
|
+
};
|
|
46
|
+
}, { readOnly: true });
|
|
47
|
+
return { ...query, name: query.data?.name ?? null, description: query.data?.description ?? null,
|
|
48
|
+
properties: query.data?.properties ?? EMPTY_PROPERTIES };
|
|
49
|
+
}
|
|
50
|
+
const EMPTY_PROPERTIES: DatabaseProperty[] = [];
|
|
@@ -36,7 +36,7 @@ export function useDatabaseSubscription(
|
|
|
36
36
|
): UseDatabaseSubscriptionResult {
|
|
37
37
|
const runtime = useNotisRuntime();
|
|
38
38
|
const { subscribe = true, ...documentOptions } = options;
|
|
39
|
-
const { documents, loading, error, refetch } = useDocuments(databaseSlug, documentOptions);
|
|
39
|
+
const { documents, loading, isFetching, hasData, error, refetch } = useDocuments(databaseSlug, documentOptions);
|
|
40
40
|
const [live, setLive] = useState(false);
|
|
41
41
|
|
|
42
42
|
const refetchRef = useRef(refetch);
|
|
@@ -72,5 +72,5 @@ export function useDatabaseSubscription(
|
|
|
72
72
|
};
|
|
73
73
|
}, [runtime, databaseSlug, enabled]);
|
|
74
74
|
|
|
75
|
-
return { documents, rows: documents, loading, error, refetch, live };
|
|
75
|
+
return { documents, rows: documents, loading, isFetching, hasData, error, refetch, live };
|
|
76
76
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useQuery } from './useQuery';
|
|
4
|
+
import { useNotisRuntime } from '../provider';
|
|
5
|
+
import { normalizeDocumentRecord } from '../documents';
|
|
6
|
+
import type { DocumentRecord } from '../runtime';
|
|
7
|
+
import { pick, type Words } from '../words/types';
|
|
8
|
+
|
|
9
|
+
/** The words of the document hook's own failures, in English and French (C8). */
|
|
10
|
+
export const DOCUMENT_HOOK_WORDS = Object.freeze({
|
|
11
|
+
notFound: { en: 'Document not found', fr: 'Document introuvable' },
|
|
12
|
+
}) satisfies Readonly<Record<string, Words>>;
|
|
13
|
+
|
|
14
|
+
interface GetDocumentResult {
|
|
15
|
+
status?: string;
|
|
16
|
+
document?: unknown;
|
|
17
|
+
message?: string;
|
|
18
|
+
error?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface UseDocumentOptions {
|
|
22
|
+
enabled?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface UseDocumentResult {
|
|
26
|
+
document: DocumentRecord | null;
|
|
27
|
+
loading: boolean;
|
|
28
|
+
isFetching: boolean;
|
|
29
|
+
hasData: boolean;
|
|
30
|
+
error: Error | null;
|
|
31
|
+
/** Reads again; resolves to the read's error, or null when it succeeded. */
|
|
32
|
+
refetch: () => Promise<Error | null>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Fetch a single document (with content) by id, normalized. */
|
|
36
|
+
export function useDocument(
|
|
37
|
+
documentId: string | null | undefined,
|
|
38
|
+
options: UseDocumentOptions = {},
|
|
39
|
+
): UseDocumentResult {
|
|
40
|
+
const runtime = useNotisRuntime();
|
|
41
|
+
const locale = runtime?.context?.locale === 'fr' ? 'fr' : 'en';
|
|
42
|
+
const query = useQuery<DocumentRecord>(['document', documentId ?? null, locale], async () => {
|
|
43
|
+
if (!runtime || !documentId) throw new Error(pick(DOCUMENT_HOOK_WORDS.notFound, locale));
|
|
44
|
+
const result = await runtime.callTool<GetDocumentResult>('LOCAL_NOTIS_DATABASE_GET_DOCUMENT', {
|
|
45
|
+
document_id: documentId,
|
|
46
|
+
}, { dedupe: true, readOnly: true });
|
|
47
|
+
if (!result.document) throw new Error(result.error ?? result.message ?? pick(DOCUMENT_HOOK_WORDS.notFound, locale));
|
|
48
|
+
return normalizeDocumentRecord(result.document, { locale });
|
|
49
|
+
}, { readOnly: true, enabled: options.enabled !== false && Boolean(documentId) });
|
|
50
|
+
return { document: query.data ?? null, loading: query.loading, isFetching: query.isFetching, hasData: query.hasData, error: query.error, refetch: query.refetch };
|
|
51
|
+
}
|