@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
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
|
|
2
|
+
/*
|
|
3
|
+
* Portal-parity theme tokens. In production the portal copies its computed
|
|
4
|
+
* :root custom properties onto the app root, so app CSS written against
|
|
5
|
+
* hsl(var(--token)) resolves to the real portal palette. The harness mirrors
|
|
6
|
+
* the portal's light theme here so dev renders and listing screenshots look
|
|
7
|
+
* exactly like the app inside the portal. Keep in sync with
|
|
8
|
+
* portal/src/app/globals.css (:root).
|
|
9
|
+
*/
|
|
10
|
+
:root {
|
|
11
|
+
color-scheme: light;
|
|
12
|
+
--background: 0 0% 100%;
|
|
13
|
+
--foreground: 222.2 84% 4.9%;
|
|
14
|
+
--card: 0 0% 100%;
|
|
15
|
+
--card-foreground: 222.2 84% 4.9%;
|
|
16
|
+
--popover: 0 0% 100%;
|
|
17
|
+
--popover-foreground: 222.2 84% 4.9%;
|
|
18
|
+
--primary: 222.2 47.4% 11.2%;
|
|
19
|
+
--primary-foreground: 210 40% 98%;
|
|
20
|
+
--secondary: 210 40% 96.1%;
|
|
21
|
+
--secondary-foreground: 222.2 47.4% 11.2%;
|
|
22
|
+
--muted: 210 40% 96.1%;
|
|
23
|
+
--muted-foreground: 0 0% 46.9%;
|
|
24
|
+
--accent: 210 40% 96.1%;
|
|
25
|
+
--accent-foreground: 222.2 47.4% 11.2%;
|
|
26
|
+
--destructive: 0 84.2% 60.2%;
|
|
27
|
+
--destructive-foreground: 210 40% 98%;
|
|
28
|
+
--border: 214.3 31.8% 91.4%;
|
|
29
|
+
--input: 214.3 31.8% 91.4%;
|
|
30
|
+
--ring: 222.2 84% 4.9%;
|
|
31
|
+
--radius: 0.5rem;
|
|
32
|
+
--chart-1: 210 100% 49%;
|
|
33
|
+
--chart-2: 173 58% 39%;
|
|
34
|
+
--chart-3: 197 37% 24%;
|
|
35
|
+
--chart-4: 43 74% 66%;
|
|
36
|
+
--chart-5: 27 87% 67%;
|
|
37
|
+
}
|
|
38
|
+
:root.dark {
|
|
39
|
+
color-scheme: dark;
|
|
40
|
+
--background: 0 0% 13%;
|
|
41
|
+
--foreground: 210 40% 98%;
|
|
42
|
+
--card: 0 0% 13%;
|
|
43
|
+
--card-foreground: 210 40% 98%;
|
|
44
|
+
--popover: 0 0% 13%;
|
|
45
|
+
--popover-foreground: 210 40% 98%;
|
|
46
|
+
--primary: 210 40% 98%;
|
|
47
|
+
--primary-foreground: 222.2 47.4% 11.2%;
|
|
48
|
+
--secondary: 0 0% 18%;
|
|
49
|
+
--secondary-foreground: 210 40% 98%;
|
|
50
|
+
--muted: 0 0% 18%;
|
|
51
|
+
--muted-foreground: 0 0% 65.1%;
|
|
52
|
+
--accent: 0 0% 18%;
|
|
53
|
+
--accent-foreground: 210 40% 98%;
|
|
54
|
+
--destructive: 0 84.2% 60.2%;
|
|
55
|
+
--destructive-foreground: 210 40% 98%;
|
|
56
|
+
--border: 0 0% 23.5%;
|
|
57
|
+
--input: 0 0% 23.5%;
|
|
58
|
+
--ring: 212.7 26.8% 83.9%;
|
|
59
|
+
--chart-1: 220 70% 50%;
|
|
60
|
+
--chart-2: 160 60% 45%;
|
|
61
|
+
--chart-3: 30 80% 55%;
|
|
62
|
+
--chart-4: 280 65% 60%;
|
|
63
|
+
--chart-5: 340 75% 55%;
|
|
64
|
+
}
|
|
65
|
+
body {
|
|
66
|
+
margin: 0;
|
|
67
|
+
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
|
68
|
+
background: hsl(var(--background));
|
|
69
|
+
color: hsl(var(--foreground));
|
|
70
|
+
-webkit-font-smoothing: antialiased;
|
|
71
|
+
}
|
|
72
|
+
#root { min-height: 100vh; }
|
|
73
|
+
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/** Evaluated in Chromium: includes open Shadow roots and explicitly declared chart semantics. */
|
|
2
|
+
export function extractRenderedMarkdown({ baseUrl = document.baseURI } = {}) {
|
|
3
|
+
const space = value => String(value ?? '').replace(/\s+/g, ' ').trim();
|
|
4
|
+
const escape = value => space(value).replaceAll('\\', '\\\\').replaceAll('|', '\\|');
|
|
5
|
+
const link = value => {
|
|
6
|
+
try { const url = new URL(value, baseUrl); return ['http:', 'https:', 'mailto:'].includes(url.protocol) ? url.href : ''; }
|
|
7
|
+
catch { return ''; }
|
|
8
|
+
};
|
|
9
|
+
const visible = node => {
|
|
10
|
+
const style = getComputedStyle(node);
|
|
11
|
+
return !node.hidden && node.getAttribute('aria-hidden') !== 'true' && style.display !== 'none' && style.visibility !== 'hidden';
|
|
12
|
+
};
|
|
13
|
+
const labelledBy = node => (node.getAttribute('aria-labelledby') || '').split(' ')
|
|
14
|
+
.map(id => node.getRootNode().getElementById?.(id)?.textContent || '').join(' ').trim();
|
|
15
|
+
const label = node => node.getAttribute('aria-label') || labelledBy(node)
|
|
16
|
+
|| (node.id ? [...node.getRootNode().querySelectorAll('label')].find(label => label.htmlFor === node.id)?.textContent : '')
|
|
17
|
+
|| node.closest('label')?.textContent || node.getAttribute('placeholder') || node.name || 'Field';
|
|
18
|
+
const table = element => {
|
|
19
|
+
const rows = [...element.querySelectorAll('tr')].map(row => [...row.children].filter(cell => ['TD', 'TH'].includes(cell.tagName)).map(cell => escape([...cell.childNodes].map(walk).join(''))));
|
|
20
|
+
if (!rows.length) return '';
|
|
21
|
+
const columns = Math.max(...rows.map(row => row.length));
|
|
22
|
+
const padded = rows.map(row => [...row, ...Array(Math.max(0, columns - row.length)).fill('')]);
|
|
23
|
+
const header = element.querySelector('tr')?.querySelector('th') ? padded.shift() : Array(columns).fill('');
|
|
24
|
+
return '\n\n' + [header, Array(columns).fill('---'), ...padded].map(row => '| ' + row.join(' | ') + ' |').join('\n') + '\n\n';
|
|
25
|
+
};
|
|
26
|
+
const children = element => [...(element.shadowRoot || element).childNodes].map(walk).join('');
|
|
27
|
+
// RenderChartData: the chart's label line, then its exact values as a table.
|
|
28
|
+
const chartTable = node => '\n\n' + space(node.getAttribute('aria-label')) + '\n' + table(node);
|
|
29
|
+
// An SVG or canvas chart contributes its accessible name and any declared chart
|
|
30
|
+
// data inside it, never mark tooltips or geometry. SVG elements keep a lowercase
|
|
31
|
+
// tagName in HTML documents, so they are matched by localName. A drawing with no
|
|
32
|
+
// name (a diagram) keeps its visible labels, one per line.
|
|
33
|
+
const graphic = node => {
|
|
34
|
+
const own = [...node.children].find(child => child.localName === 'title');
|
|
35
|
+
const name = space(node.getAttribute('aria-label') || labelledBy(node) || own?.textContent);
|
|
36
|
+
const charts = [...node.querySelectorAll('[data-notis-render="chart-data"]')].map(chartTable).join('');
|
|
37
|
+
if (name || node.localName !== 'svg') return (name ? '\n\n' + name + '\n\n' : '') + charts;
|
|
38
|
+
const labels = [...node.querySelectorAll('text, foreignObject')].filter(visible)
|
|
39
|
+
.map(item => item.localName === 'text' ? space(item.textContent) : children(item));
|
|
40
|
+
return '\n\n' + labels.join('\n\n') + '\n\n';
|
|
41
|
+
};
|
|
42
|
+
const descriptionList = element => {
|
|
43
|
+
const lines = [], terms = [], definitions = [];
|
|
44
|
+
const flush = () => {
|
|
45
|
+
if (terms.length || definitions.length) lines.push('- ' + (terms.length ? terms.join(' / ') + ': ' : '') + definitions.join('; '));
|
|
46
|
+
terms.length = 0; definitions.length = 0;
|
|
47
|
+
};
|
|
48
|
+
// SDK properties wrap each dt/dd pair in a div. Keep those pairs and HTML's
|
|
49
|
+
// multi-term/multi-definition groups, but exclude nested lists and hidden wrappers.
|
|
50
|
+
for (const node of element.querySelectorAll('dt,dd')) {
|
|
51
|
+
if (node.closest('dl') !== element) continue;
|
|
52
|
+
let shown = true;
|
|
53
|
+
for (let current = node; current && current !== element; current = current.parentElement) {
|
|
54
|
+
if (!visible(current)) { shown = false; break; }
|
|
55
|
+
}
|
|
56
|
+
if (!shown) continue;
|
|
57
|
+
if (node.tagName === 'DT') {
|
|
58
|
+
if (definitions.length) flush();
|
|
59
|
+
terms.push(space(children(node)));
|
|
60
|
+
} else definitions.push(space(children(node)));
|
|
61
|
+
}
|
|
62
|
+
flush();
|
|
63
|
+
return '\n\n' + lines.join('\n') + '\n\n';
|
|
64
|
+
};
|
|
65
|
+
function walk(node) {
|
|
66
|
+
if (node.nodeType === Node.TEXT_NODE) return node.textContent || '';
|
|
67
|
+
if (node.nodeType !== Node.ELEMENT_NODE) return '';
|
|
68
|
+
const tag = node.tagName;
|
|
69
|
+
if (['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEMPLATE', 'LINK', 'META'].includes(tag)) return '';
|
|
70
|
+
const semantic = node.getAttribute('data-notis-render');
|
|
71
|
+
if (semantic === 'markdown') return '\n\n' + (node.textContent || '') + '\n\n';
|
|
72
|
+
if (semantic === 'chart-data') return chartTable(node);
|
|
73
|
+
if (!visible(node)) return '';
|
|
74
|
+
if (tag === 'TABLE') return table(node);
|
|
75
|
+
if (tag === 'DL') return descriptionList(node);
|
|
76
|
+
if (/^H[1-6]$/.test(tag)) return '\n\n' + '#'.repeat(Number(tag[1])) + ' ' + space(children(node)) + '\n\n';
|
|
77
|
+
if (tag === 'A') { const url = link(node.getAttribute('href')); const text = space(children(node)); return url ? `[${text || url}](${url})` : text; }
|
|
78
|
+
if (tag === 'IMG') { const url = link(node.currentSrc || node.src); return url ? `` : space(node.alt); }
|
|
79
|
+
if (tag === 'PRE') return '\n\n```' + (node.getAttribute('data-language') || '') + '\n' + node.textContent.trim() + '\n```\n\n';
|
|
80
|
+
if (tag === 'CODE') return '`' + node.textContent.replaceAll('`', '\\`') + '`';
|
|
81
|
+
if (['STRONG', 'B'].includes(tag)) return '**' + children(node) + '**';
|
|
82
|
+
if (['EM', 'I'].includes(tag)) return '*' + children(node) + '*';
|
|
83
|
+
if (tag === 'BR') return '\n';
|
|
84
|
+
if (tag === 'HR') return '\n\n---\n\n';
|
|
85
|
+
if (tag === 'LI') {
|
|
86
|
+
const nested = [...node.children].filter(child => ['UL', 'OL'].includes(child.tagName));
|
|
87
|
+
const text = space([...node.childNodes].filter(child => !nested.includes(child)).map(walk).join(''));
|
|
88
|
+
const prefix = node.parentElement?.tagName === 'OL' ? `${[...node.parentElement.children].indexOf(node) + 1}. ` : '- ';
|
|
89
|
+
return '\n' + prefix + text + nested.map(list => children(list).trim().split('\n').map(line => '\n ' + line).join('')).join('');
|
|
90
|
+
}
|
|
91
|
+
if (tag === 'BLOCKQUOTE') return '\n\n' + children(node).trim().split('\n').map(line => '> ' + line).join('\n') + '\n\n';
|
|
92
|
+
if (['INPUT', 'TEXTAREA', 'SELECT'].includes(tag)) {
|
|
93
|
+
if (['password', 'hidden'].includes(node.type)) return '';
|
|
94
|
+
const value = node.type === 'checkbox' || node.type === 'radio' ? (node.checked ? '[x]' : '[ ]')
|
|
95
|
+
: tag === 'SELECT' ? [...node.selectedOptions].map(option => option.text).join(', ') : node.value;
|
|
96
|
+
return '\n\n' + escape(label(node)) + ': ' + escape(value) + '\n\n';
|
|
97
|
+
}
|
|
98
|
+
if (tag === 'BUTTON') return ' ' + space(node.textContent) + ' ';
|
|
99
|
+
if (node.localName === 'svg' || tag === 'CANVAS') return graphic(node);
|
|
100
|
+
const content = children(node);
|
|
101
|
+
return ['P', 'DIV', 'SECTION', 'ARTICLE', 'HEADER', 'FOOTER', 'MAIN', 'UL', 'OL', 'FIGURE'].includes(tag) ? '\n\n' + content + '\n\n' : content;
|
|
102
|
+
}
|
|
103
|
+
return walk(document.body).replace(/[ \t]+\n/g, '\n').replace(/\n{3,}/g, '\n\n').trim();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Bounded capture expands clipped content; no 1500px crop or hidden inner scroll tail. */
|
|
107
|
+
export function expandScrollAreas({ maxHeight = 60000 } = {}) {
|
|
108
|
+
const nodes = [];
|
|
109
|
+
const visit = root => { for (const node of root.querySelectorAll('*')) { nodes.push(node); if (node.shadowRoot) visit(node.shadowRoot); } };
|
|
110
|
+
visit(document);
|
|
111
|
+
let expanded = 0;
|
|
112
|
+
for (const node of nodes.reverse()) {
|
|
113
|
+
const style = getComputedStyle(node);
|
|
114
|
+
if (style.display === 'none' || style.visibility === 'hidden') continue;
|
|
115
|
+
if (node.scrollHeight > maxHeight) return { tooLarge: true, height: node.scrollHeight };
|
|
116
|
+
// Descendants come first, so an ancestor holding an expanded area (even with
|
|
117
|
+
// visible overflow, or a height frozen by freezeFrameViewportSizes) grows with it
|
|
118
|
+
// instead of letting the following content paint over the expanded tail.
|
|
119
|
+
if (node.scrollHeight > node.clientHeight + 2 && (/(auto|scroll|hidden|clip)/.test(style.overflowY) || node.hasAttribute('data-notis-render-expanded')
|
|
120
|
+
|| node.querySelector('[data-notis-render-expanded]') || node.shadowRoot?.querySelector('[data-notis-render-expanded]'))) {
|
|
121
|
+
node.setAttribute('data-notis-render-expanded', 'true');
|
|
122
|
+
node.style.setProperty('height', `${node.scrollHeight}px`, 'important');
|
|
123
|
+
node.style.setProperty('max-height', 'none', 'important');
|
|
124
|
+
node.style.setProperty('overflow-y', 'visible', 'important');
|
|
125
|
+
const parent = node.parentElement || node.getRootNode().host;
|
|
126
|
+
const parentStyle = parent && getComputedStyle(parent);
|
|
127
|
+
// A column flex item sizes from its basis, not its height: flex-1 (basis 0%)
|
|
128
|
+
// would keep the expanded area at its old size. Only the vertical axis may
|
|
129
|
+
// change; a row child's basis/shrink would widen it and stop copy wrapping.
|
|
130
|
+
if (parentStyle?.display.includes('flex') && parentStyle.flexDirection.startsWith('column')) {
|
|
131
|
+
node.style.setProperty('flex-shrink', '0', 'important');
|
|
132
|
+
node.style.setProperty('flex-basis', 'auto', 'important');
|
|
133
|
+
}
|
|
134
|
+
expanded++;
|
|
135
|
+
}
|
|
136
|
+
if (style.position === 'fixed' || style.position === 'sticky') node.style.setProperty('position', 'static', 'important');
|
|
137
|
+
}
|
|
138
|
+
document.documentElement.style.setProperty('height', 'auto', 'important');
|
|
139
|
+
document.body.style.setProperty('height', 'auto', 'important');
|
|
140
|
+
const height = Math.max(document.documentElement.scrollHeight, document.body.scrollHeight);
|
|
141
|
+
return { expanded, height, tooLarge: height > maxHeight };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* What Playwright's screenshot options animations: 'disabled' and caret: 'hide'
|
|
146
|
+
* do, for the scrolled tiles captured outside page.screenshot, applied in the
|
|
147
|
+
* document and every shadow root: finite animations jump to their end state,
|
|
148
|
+
* infinite ones return to their start, a paused one (playback rate zero) stays
|
|
149
|
+
* as it is, animations that start while the tiles are captured are handled the
|
|
150
|
+
* same way, and no text caret blinks into one tile and out of the next.
|
|
151
|
+
*/
|
|
152
|
+
export function freezeAnimations() {
|
|
153
|
+
const roots = [];
|
|
154
|
+
const collect = root => {
|
|
155
|
+
roots.push(root);
|
|
156
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
|
|
157
|
+
do { if (walker.currentNode.shadowRoot) collect(walker.currentNode.shadowRoot); } while (walker.nextNode());
|
|
158
|
+
};
|
|
159
|
+
collect(document);
|
|
160
|
+
const settle = root => {
|
|
161
|
+
for (const animation of root.getAnimations()) {
|
|
162
|
+
if (!animation.effect || animation.playbackRate === 0) continue;
|
|
163
|
+
try {
|
|
164
|
+
if (Number.isFinite(animation.effect.getComputedTiming().endTime)) animation.finish(); else animation.cancel();
|
|
165
|
+
} catch { /* An animation that refuses to finish or cancel stays as it is. */ }
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
for (const root of roots) {
|
|
169
|
+
settle(root);
|
|
170
|
+
root.addEventListener('transitionrun', () => settle(root));
|
|
171
|
+
root.addEventListener('animationstart', () => settle(root));
|
|
172
|
+
root.querySelectorAll('input,textarea,[contenteditable]').forEach(element => element.style.setProperty('caret-color', 'transparent', 'important'));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function restrictBrowserTransports() {
|
|
177
|
+
const blocked = function () { throw new Error('External transports are unavailable in a read-only render.'); };
|
|
178
|
+
for (const key of ['RTCPeerConnection', 'webkitRTCPeerConnection', 'Worker', 'SharedWorker']) {
|
|
179
|
+
try { Object.defineProperty(globalThis, key, { value: blocked, writable: false, configurable: false }); } catch {}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Preserve each child frame's original viewport-sized layout while enlarging
|
|
184
|
+
* its capture box. Otherwise a 100vh child grows again on every iframe resize. */
|
|
185
|
+
export function freezeFrameViewportSizes() {
|
|
186
|
+
const visit = root => {
|
|
187
|
+
for (const node of root.querySelectorAll('*')) {
|
|
188
|
+
const style = getComputedStyle(node);
|
|
189
|
+
if (style.display !== 'none') {
|
|
190
|
+
for (const key of ['height', 'min-height', 'max-height']) {
|
|
191
|
+
const value = style.getPropertyValue(key);
|
|
192
|
+
if (/^[0-9.]+px$/.test(value) && Number.parseFloat(value) > 0) node.style.setProperty(key, value, 'important');
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (node.shadowRoot) visit(node.shadowRoot);
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
visit(document);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export async function settleFrameImages() {
|
|
202
|
+
const images = [];
|
|
203
|
+
const collect = root => { for (const element of root.querySelectorAll('*')) {
|
|
204
|
+
if (element.tagName === 'IMG' && element.getAttribute('src')) images.push(element);
|
|
205
|
+
if (element.shadowRoot) collect(element.shadowRoot);
|
|
206
|
+
} };
|
|
207
|
+
collect(document);
|
|
208
|
+
return (await Promise.all(images.map(async image => {
|
|
209
|
+
image.loading = 'eager';
|
|
210
|
+
try { await image.decode(); return true; } catch { return false; }
|
|
211
|
+
}))).every(Boolean);
|
|
212
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export class ViewRenderError extends Error {
|
|
2
|
+
constructor(code, message, status = 409) { super(message); this.name = 'ViewRenderError'; this.code = code; this.status = status; }
|
|
3
|
+
}
|
|
4
|
+
export function renderFailure(code = 'render_failed') {
|
|
5
|
+
// Browser/source errors may contain private content. Never return them before
|
|
6
|
+
// final authority revalidation, or log tokens/source/request arguments.
|
|
7
|
+
return new ViewRenderError(code, 'The view could not be rendered. Reopen its current link and try again.');
|
|
8
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import * as ReactDOM from 'react-dom';
|
|
3
|
+
import * as ReactDOMClient from 'react-dom/client';
|
|
4
|
+
import * as JSXRuntime from 'react/jsx-runtime';
|
|
5
|
+
import { BlockNoteEditor } from '@blocknote/core';
|
|
6
|
+
import { BlockNoteView } from '@blocknote/ariakit';
|
|
7
|
+
import { useCreateBlockNote } from '@blocknote/react';
|
|
8
|
+
import '@blocknote/core/fonts/inter.css';
|
|
9
|
+
import '@blocknote/react/style.css';
|
|
10
|
+
import '@blocknote/ariakit/style.css';
|
|
11
|
+
import { getNotisBlockNoteEditorOptions, getDocumentEditorInitialContent, NOTIS_BLOCKNOTE_TABLE_OPTIONS } from '../../../portal/src/lib/blockNote';
|
|
12
|
+
import { htmlWithViewerPolicy } from '../../../portal/src/lib/htmlFramePolicy';
|
|
13
|
+
import { importBundleSource, resolveBundleRouteExport } from '../../sdk/src/bundleModule';
|
|
14
|
+
import { SdkPresentation } from '../../sdk/src/presentation';
|
|
15
|
+
import { createQueryClient } from '../../sdk/src/queryCache';
|
|
16
|
+
import { Markdown } from '../../sdk/src/components/Markdown';
|
|
17
|
+
import { useNotisRuntime } from '../../sdk/src/provider';
|
|
18
|
+
import * as NotisSDK from '../../sdk/src/index';
|
|
19
|
+
import * as NotisSDKUI from '../../sdk/src/ui/index';
|
|
20
|
+
// The toolkit stylesheet the host adopts into every Space shadow root (esbuild text import).
|
|
21
|
+
import toolkitCss from '../../sdk/src/ui/toolkit.css' with { type: 'text' };
|
|
22
|
+
|
|
23
|
+
const state = { mounted: false, pending: 0, blocked: [] as string[], lastActivity: performance.now(), failed: false };
|
|
24
|
+
// The Space runtime contract (packages/sdk/src/runtimeModules.ts): bundles import React and the SDK from these.
|
|
25
|
+
Object.assign(window, { __notisRenderState: state, React, ReactDOM, ReactDOMClient, __ReactJSXRuntime: JSXRuntime,
|
|
26
|
+
__NotisSDK: NotisSDK, __NotisSDKUI: NotisSDKUI });
|
|
27
|
+
NotisSDKUI.registerToolkitStyles(toolkitCss);
|
|
28
|
+
let sequence = 0;
|
|
29
|
+
let locale = 'en';
|
|
30
|
+
const copy = { en: { body: 'Document body', unavailable: 'Record unavailable', opening: 'Opening record…', html: 'HTML document', view: 'View unavailable' },
|
|
31
|
+
fr: { body: 'Contenu du document', unavailable: 'Enregistrement indisponible', opening: 'Ouverture de l’enregistrement…', html: 'Document HTML', view: 'Vue indisponible' } };
|
|
32
|
+
const text = key => copy[locale === 'fr' ? 'fr' : 'en'][key];
|
|
33
|
+
const changed = () => { state.lastActivity = performance.now(); };
|
|
34
|
+
function denied() {
|
|
35
|
+
const error = Object.assign(new Error('This operation is unavailable in a read-only render.'), { code: 'render_read_only' });
|
|
36
|
+
state.blocked.push(error.code); changed();
|
|
37
|
+
return Promise.reject(error);
|
|
38
|
+
}
|
|
39
|
+
window.addEventListener('unhandledrejection', event => { if (event.reason?.code === 'render_read_only') event.preventDefault(); else state.failed = true; });
|
|
40
|
+
async function read(payload) {
|
|
41
|
+
state.pending++; changed();
|
|
42
|
+
try {
|
|
43
|
+
const response = await fetch('/__read', { method: 'POST', credentials: 'omit', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
|
|
44
|
+
const result = await response.json();
|
|
45
|
+
if (!response.ok) throw Object.assign(new Error(result.message || 'This read is unavailable.'), { code: result.error });
|
|
46
|
+
return result.result;
|
|
47
|
+
} finally { state.pending--; changed(); }
|
|
48
|
+
}
|
|
49
|
+
function useRead(payload) {
|
|
50
|
+
const key = JSON.stringify(payload);
|
|
51
|
+
const [loaded, setLoaded] = useState<{ key: string; value?: any; failed?: boolean }>();
|
|
52
|
+
useEffect(() => { let active = true; void read(payload).then(value => { if (active) setLoaded({ key, value }); })
|
|
53
|
+
.catch(() => { if (active) setLoaded({ key, failed: true }); }); return () => { active = false; }; }, [key]);
|
|
54
|
+
return loaded?.key === key ? loaded : {};
|
|
55
|
+
}
|
|
56
|
+
// A page's `properties` choice ({ include, exclude, order } by property name) picks and orders the rows it shows.
|
|
57
|
+
function chosenFields(value, choice?) {
|
|
58
|
+
let fields = (Object.values(value.schema?.properties || {}) as any[]).filter(field => field.kind !== 'title');
|
|
59
|
+
if (!choice || typeof choice !== 'object') return fields;
|
|
60
|
+
if (Array.isArray(choice.include)) fields = fields.filter(field => choice.include.includes(field.name));
|
|
61
|
+
if (Array.isArray(choice.exclude)) fields = fields.filter(field => !choice.exclude.includes(field.name));
|
|
62
|
+
const order = Array.isArray(choice.order) ? choice.order : [];
|
|
63
|
+
const rank = field => (order.includes(field.name) ? order.indexOf(field.name) : order.length);
|
|
64
|
+
return fields.map((field, index) => ({ field, index })).sort((a, b) => rank(a.field) - rank(b.field) || a.index - b.index).map(item => item.field);
|
|
65
|
+
}
|
|
66
|
+
function PropertyList({ value, choice = undefined }: { value: any; choice?: any }) {
|
|
67
|
+
const fields = chosenFields(value, choice);
|
|
68
|
+
const text = (property: any) => {
|
|
69
|
+
if (!property) return '';
|
|
70
|
+
const item = property[property.type];
|
|
71
|
+
if (['rich_text', 'title', 'text'].includes(property.type)) return (property.rich_text || property.title || []).map(part => part.plain_text || part.text?.content || '').join('');
|
|
72
|
+
if (Array.isArray(item)) return item.map(value => value.name || value.url || value.external?.url || value.id || '').join(', ');
|
|
73
|
+
if (item && typeof item === 'object') return item.name || item.start || '';
|
|
74
|
+
return item == null ? '' : String(item);
|
|
75
|
+
};
|
|
76
|
+
// A long value (a URL) wraps inside the page instead of running past its edge.
|
|
77
|
+
return <dl>{fields.map(field => <div key={field.id}>
|
|
78
|
+
<dt>{field.name}</dt><dd style={{ overflowWrap: 'anywhere' }}>{text(value.document?.properties?.[field.name])}</dd>
|
|
79
|
+
</div>)}</dl>;
|
|
80
|
+
}
|
|
81
|
+
function ReadOnlyBlocks({ blocks }) {
|
|
82
|
+
// Pure snapshot editor: no collaboration provider/token/init or uploadFile.
|
|
83
|
+
const editor = useCreateBlockNote({ ...getNotisBlockNoteEditorOptions(),
|
|
84
|
+
initialContent: getDocumentEditorInitialContent(blocks, false), tables: NOTIS_BLOCKNOTE_TABLE_OPTIONS,
|
|
85
|
+
domAttributes: { editor: { 'aria-label': text('body') } },
|
|
86
|
+
}, [blocks]);
|
|
87
|
+
return <BlockNoteView editor={editor} editable={false} theme="light" />;
|
|
88
|
+
}
|
|
89
|
+
function DocumentEditor({ recordKey, variant = 'full', showProperties = true, className }) {
|
|
90
|
+
const loaded = useRead({ operation: 'record', record_key: recordKey });
|
|
91
|
+
if (!loaded.value) return <div role="status">{text(loaded.failed ? 'unavailable' : 'opening')}</div>;
|
|
92
|
+
const value = loaded.value;
|
|
93
|
+
if (value.document?.view_type === 'html') return <HtmlFrame recordKey={recordKey} className={className} />;
|
|
94
|
+
if (value.document?.view_type === 'report') return <ReportFrame recordKey={recordKey} className={className} />;
|
|
95
|
+
return <article className={className}>{variant === 'full' ? <h1>{value.document?.title}</h1> : null}
|
|
96
|
+
{showProperties ? <PropertyList value={value} /> : null}<ReadOnlyBlocks blocks={value.document?.content_blocknote} /></article>;
|
|
97
|
+
}
|
|
98
|
+
// DocumentPage as its read-only snapshot: no toolbar, live session or saves, but the Space's own header, properties
|
|
99
|
+
// choice, slots and layout, so a render shows the page the Space draws. A layout receives the same five parts.
|
|
100
|
+
function DocumentPage(props) {
|
|
101
|
+
// An explicit HTML viewer needs only the stored file, without a record-body read.
|
|
102
|
+
return props.viewer === 'html'
|
|
103
|
+
? <HtmlFrame recordKey={props.recordKey} className={props.className} />
|
|
104
|
+
: <RecordDocumentPage {...props} />;
|
|
105
|
+
}
|
|
106
|
+
function RecordDocumentPage({ recordKey, className, header, meta, properties, aboveBody, belowBody, layout }) {
|
|
107
|
+
const loaded = useRead({ operation: 'record', record_key: recordKey });
|
|
108
|
+
if (!loaded.value) return <div role="status">{text(loaded.failed ? 'unavailable' : 'opening')}</div>;
|
|
109
|
+
const value = loaded.value;
|
|
110
|
+
if (value.document?.view_type === 'html') return <HtmlFrame recordKey={recordKey} className={className} />;
|
|
111
|
+
if (value.document?.view_type === 'report') return <ReportFrame recordKey={recordKey} className={className} />;
|
|
112
|
+
const showTitle = header !== false && header?.title !== false;
|
|
113
|
+
const parts = {
|
|
114
|
+
Toolbar: () => null,
|
|
115
|
+
Header: () => (showTitle ? <h1>{value.document?.title}</h1> : null),
|
|
116
|
+
Meta: () => null,
|
|
117
|
+
Properties: () => (properties === 'hidden' ? null : <PropertyList value={value} choice={properties} />),
|
|
118
|
+
Body: () => <ReadOnlyBlocks blocks={value.document?.content_blocknote} />,
|
|
119
|
+
};
|
|
120
|
+
if (typeof layout === 'function') return <div className={className}>{layout(parts)}</div>;
|
|
121
|
+
// Without a layout the properties sit in the meta strip's panel, which a page with `meta={false}` does not draw.
|
|
122
|
+
return <article className={className}><parts.Header />{meta === false ? null : <parts.Properties />}{aboveBody}<parts.Body />{belowBody}</article>;
|
|
123
|
+
}
|
|
124
|
+
function RecordProperties({ recordKey, className }) {
|
|
125
|
+
const loaded = useRead({ operation: 'record', record_key: recordKey });
|
|
126
|
+
return <div className={className}>{loaded.value ? <PropertyList value={loaded.value} /> : null}</div>;
|
|
127
|
+
}
|
|
128
|
+
function HtmlFrame(props) {
|
|
129
|
+
return props.html !== undefined
|
|
130
|
+
? <HtmlValue html={props.html} title={props.title} className={props.className} onReady={props.onReady} />
|
|
131
|
+
: <HtmlRecord {...props} />;
|
|
132
|
+
}
|
|
133
|
+
function HtmlRecord({ recordKey, title, className, onReady }) {
|
|
134
|
+
const loaded = useRead({ operation: 'html', record_key: recordKey });
|
|
135
|
+
// As live, a record that cannot load is ready too, so a view waiting on it does not stay blank.
|
|
136
|
+
useEffect(() => { if (loaded.failed) onReady?.(); }, [loaded.failed]);
|
|
137
|
+
return loaded.value ? <HtmlValue html={loaded.value.html} title={title || loaded.value.title} className={className} onReady={onReady} /> : null;
|
|
138
|
+
}
|
|
139
|
+
// The live sandbox (HtmlDocumentFrame): the same flags and the same viewer CSP prepended to the HTML. As live, a
|
|
140
|
+
// className sizes a box the frame fills (a 1920x1080 slide box); without one the frame keeps its full-page height.
|
|
141
|
+
// onReady fires once per page after it loads; a preview takes no presenter input, so `presentation` changes nothing.
|
|
142
|
+
function HtmlValue({ html, title, className, onReady }) {
|
|
143
|
+
const reported = React.useRef<string>(undefined);
|
|
144
|
+
const loaded = () => { if (reported.current === html) return; reported.current = html; onReady?.(); };
|
|
145
|
+
const frame = <iframe sandbox="allow-scripts allow-popups allow-popups-to-escape-sandbox" referrerPolicy="no-referrer"
|
|
146
|
+
title={title || text('html')} srcDoc={htmlWithViewerPolicy(html)} onLoad={loaded}
|
|
147
|
+
style={className ? { display: 'block', width: '100%', height: '100%', border: 0 } : { display: 'block', width: '100%', height: 900, border: 0 }} />;
|
|
148
|
+
return className ? <div className={className}>{frame}</div> : frame;
|
|
149
|
+
}
|
|
150
|
+
function ReportFrame({ recordKey, className }) {
|
|
151
|
+
const loaded = useRead({ operation: 'report', record_key: recordKey });
|
|
152
|
+
const [module, setModule] = useState<any>();
|
|
153
|
+
useEffect(() => { let active = true; if (loaded.value?.source) { state.pending++; changed();
|
|
154
|
+
void importBundleSource(loaded.value.source).then(value => {
|
|
155
|
+
if (!resolveBundleRouteExport(value, [loaded.value.export_name])) { state.failed = true; return; }
|
|
156
|
+
if (active) setModule(value);
|
|
157
|
+
}).catch(() => { state.failed = true; }).finally(() => { state.pending--; changed(); });
|
|
158
|
+
} return () => { active = false; }; }, [loaded.value]);
|
|
159
|
+
const View = module && loaded.value ? resolveBundleRouteExport(module, [loaded.value.export_name])?.component : null;
|
|
160
|
+
return <div className={className}>{loaded.value?.css ? <style>{loaded.value.css}</style> : null}{View ? <View /> : null}</div>;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function start() {
|
|
164
|
+
const response = await fetch('/__snapshot', { credentials: 'omit' });
|
|
165
|
+
if (!response.ok) throw new Error('Render unavailable');
|
|
166
|
+
const snapshot = await response.json();
|
|
167
|
+
locale = snapshot.locale || 'en';
|
|
168
|
+
document.documentElement.lang = locale;
|
|
169
|
+
const authority = structuredClone(snapshot.descriptor.space);
|
|
170
|
+
const shown = {};
|
|
171
|
+
let activeMarkdown: string | null = null;
|
|
172
|
+
const runtime = {
|
|
173
|
+
...snapshot.descriptor, databases: [], queryClient: createQueryClient({ maxEntries: 100 }),
|
|
174
|
+
context: { ...snapshot.descriptor.context, locale, params: Object.fromEntries(Object.entries(snapshot.params || {}).map(([key, value]) => [key, String(value)])), renderReadOnly: true },
|
|
175
|
+
listTools: denied, callTool: denied, request: denied,
|
|
176
|
+
// A view-params change only moves this render's own URL (a list opening its first item), never a write.
|
|
177
|
+
navigate: target => (target?.kind === 'view-params' ? Promise.resolve() : denied()),
|
|
178
|
+
async documentBody(request) {
|
|
179
|
+
if (request?.operation !== 'read') return denied();
|
|
180
|
+
state.pending++; changed();
|
|
181
|
+
try {
|
|
182
|
+
const value = await read({ operation: 'document_body', record_key: request.recordKey, binding: request.binding, read_action: request.readAction });
|
|
183
|
+
if (typeof value.content_markdown === 'string') return value; // Pure offline body fixtures, never a writable room.
|
|
184
|
+
const editor = BlockNoteEditor.create({ ...getNotisBlockNoteEditorOptions(), tables: NOTIS_BLOCKNOTE_TABLE_OPTIONS });
|
|
185
|
+
try { return { ...value, content_markdown: await editor.blocksToMarkdownLossy(value.content_blocknote) }; }
|
|
186
|
+
finally { editor._tiptapEditor.destroy(); }
|
|
187
|
+
} finally { state.pending--; changed(); }
|
|
188
|
+
},
|
|
189
|
+
handover: undefined, draftHandover: undefined, addContext: denied, updateContext: denied, removeContext: denied,
|
|
190
|
+
captureContextSelection() {}, registerTopBarSearch() {}, setTopBarSearchValue() {}, setTopBarSearchLoading() {},
|
|
191
|
+
publishActiveResource(value) { activeMarkdown = value?.snapshot?.format === 'markdown' && typeof value.snapshot.content === 'string' ? value.snapshot.content : null; },
|
|
192
|
+
// The document page renders as its read-only snapshot: no toolbar, live session or saves.
|
|
193
|
+
// A note opened with the HTML viewer shows its one stored HTML file, never the editor.
|
|
194
|
+
ui: { DocumentEditor, RecordProperties, HtmlFrame, ReportFrame, DocumentPage, ShareControl: () => null,
|
|
195
|
+
MarkdownEditor: ({ value }) => <Markdown value={value} /> },
|
|
196
|
+
callAction(id, inputs = {}) {
|
|
197
|
+
if (!Object.hasOwn(authority.actions, id) || authority.actions[id].readOnly !== true) return denied();
|
|
198
|
+
return read({ operation: 'action', action_id: id, inputs, request_id: String(++sequence) });
|
|
199
|
+
},
|
|
200
|
+
async shown(name, params, options = {}) {
|
|
201
|
+
const list = authority.shows?.[name];
|
|
202
|
+
if (!list || !('database' in list)) return denied();
|
|
203
|
+
const values = { ...(options.sorts ? { sorts: options.sorts } : {}), ...(options.pageSize ? { page_size: options.pageSize } : {}),
|
|
204
|
+
...(options.offset ? { offset: options.offset } : {}), ...(options.includeContent ? { include_content: true } : {}) };
|
|
205
|
+
const result = await read({ operation: 'shown', name, params, options: values }); shown[name] = result; return result;
|
|
206
|
+
},
|
|
207
|
+
viewerRead: (name, input = {}) => read({ operation: 'viewer_read', name, input }),
|
|
208
|
+
};
|
|
209
|
+
const module = await importBundleSource(snapshot.source);
|
|
210
|
+
const view = resolveBundleRouteExport(module, ['SpaceView'], true);
|
|
211
|
+
if (!view) throw new Error('View unavailable');
|
|
212
|
+
Object.assign(window, { __notisRenderMarkdown: async () => {
|
|
213
|
+
if (snapshot.markdownExport) {
|
|
214
|
+
if (typeof module[snapshot.markdownExport] !== 'function') throw new Error('The declared Markdown export is unavailable.');
|
|
215
|
+
return await (module[snapshot.markdownExport] as any)({ params: snapshot.params, shown });
|
|
216
|
+
}
|
|
217
|
+
return activeMarkdown;
|
|
218
|
+
} });
|
|
219
|
+
// The page frames the view as the Portal's SpaceSurface does: html, body, the shadow host,
|
|
220
|
+
// its mount and the app root all fill the viewport height (constrainRuntimeAppFrame), so an
|
|
221
|
+
// h-full Space root reaches the bottom of the window instead of stopping at its content.
|
|
222
|
+
for (const element of [document.documentElement, document.body]) {
|
|
223
|
+
element.style.setProperty('height', '100%'); element.style.setProperty('min-height', '100vh'); element.style.setProperty('margin', '0');
|
|
224
|
+
}
|
|
225
|
+
const host = document.createElement('div'); host.style.cssText = 'height:100vh;min-height:100vh';
|
|
226
|
+
const shadow = host.attachShadow({ mode: 'open' });
|
|
227
|
+
const styles = document.createElement('style'); styles.textContent = (snapshot.hostCss || '') + '\n' + snapshot.css; shadow.appendChild(styles);
|
|
228
|
+
NotisSDKUI.adoptToolkitStyles(shadow);
|
|
229
|
+
const mount = document.createElement('div'); mount.style.cssText = 'height:100%;min-height:0'; shadow.appendChild(mount);
|
|
230
|
+
document.body.appendChild(host);
|
|
231
|
+
const controller = document.createElement('div'); document.body.appendChild(controller);
|
|
232
|
+
// Any page area the Space leaves uncovered shows the Space theme background, not the
|
|
233
|
+
// harness white: the Space tokens live in the shadow root, so read them from the app root.
|
|
234
|
+
const frame = () => {
|
|
235
|
+
const appRoot = mount.querySelector<HTMLElement>('[data-notis-app-root]');
|
|
236
|
+
if (!appRoot) return;
|
|
237
|
+
appRoot.style.setProperty('height', '100%');
|
|
238
|
+
const token = getComputedStyle(appRoot).getPropertyValue('--background').trim();
|
|
239
|
+
const colour = [`hsl(${token})`, token].find(value => token && CSS.supports('color', value));
|
|
240
|
+
for (const element of [document.documentElement, document.body]) if (colour) element.style.setProperty('background-color', colour);
|
|
241
|
+
};
|
|
242
|
+
ReactDOMClient.createRoot(controller).render(<SdkPresentation mount={mount} runtime={runtime as any} component={view.component}
|
|
243
|
+
shell={module.__AppShell as any} theme={{ mode: 'light', style: { colorScheme: 'light' } }} resetKey={snapshot.cacheKey}
|
|
244
|
+
fallback={() => <div role="alert">{text('view')}</div>} onError={() => { state.failed = true; }}
|
|
245
|
+
// A read of an action the Space does not hold (its grant is off) is refused as unavailable, as it is
|
|
246
|
+
// live, and the view shows its own error state; every other protocol error is a blocked attempt.
|
|
247
|
+
onProtocolError={error => { if ((error as any)?.code === 'notis_sdk_query:actionUnavailable') return; state.blocked.push('render_read_only'); changed(); }}
|
|
248
|
+
onCommit={mounted => { frame(); state.mounted = mounted; changed(); }} />);
|
|
249
|
+
}
|
|
250
|
+
void start().catch(() => { state.failed = true; });
|