@notis_ai/cli 0.2.17 → 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.
Files changed (395) hide show
  1. package/README.md +542 -146
  2. package/bin/check-runtime.js +15 -0
  3. package/bin/notis.js +2 -0
  4. package/config/notis_app_design_rules.json +320 -0
  5. package/config/portal-route-roots.json +45 -0
  6. package/dist/agent-hooks/notis-agent-hook.mjs +42323 -18258
  7. package/dist/base-skills/notis-cli/.notis-base-skill.json +1 -0
  8. package/dist/base-skills/notis-cli/SKILL.md +51 -280
  9. package/dist/base-skills/notis-cli/references/app-delivery.md +17 -0
  10. package/dist/base-skills/notis-cli/references/intelligence.md +94 -0
  11. package/dist/base-skills/notis-cli/references/native-databases.md +21 -0
  12. package/dist/base-skills/notis-cli/references/tool-examples.md +56 -0
  13. package/dist/base-skills/notis-cli/references/troubleshooting.md +67 -0
  14. package/dist/base-skills/notis-query/.notis-base-skill.json +1 -0
  15. package/dist/base-skills/notis-query/SKILL.md +35 -699
  16. package/dist/base-skills/notis-query/references/database-discovery.md +59 -0
  17. package/dist/base-skills/notis-query/references/documents.md +74 -0
  18. package/dist/base-skills/notis-query/references/query.md +76 -0
  19. package/dist/base-skills/notis-spaces/.notis-base-skill.json +1 -0
  20. package/dist/base-skills/notis-spaces/SKILL.md +218 -0
  21. package/dist/base-skills/notis-spaces/references/architecture.md +116 -0
  22. package/dist/base-skills/notis-spaces/references/context.md +81 -0
  23. package/dist/base-skills/notis-spaces/references/design.md +141 -0
  24. package/dist/base-skills/notis-spaces/references/fixtures.md +29 -0
  25. package/dist/base-skills/notis-spaces/references/memory.md +58 -0
  26. package/dist/base-skills/notis-spaces/references/reading.md +73 -0
  27. package/dist/base-skills/notis-spaces/references/release.md +120 -0
  28. package/dist/base-skills/notis-spaces/references/reports.md +77 -0
  29. package/dist/base-skills/notis-spaces/references/sdk.md +273 -0
  30. package/dist/base-skills/notis-spaces/references/store.md +118 -0
  31. package/dist/base-skills/notis-spaces/references/troubleshooting.md +28 -0
  32. package/dist/base-skills/notis-spaces/references/views.md +229 -0
  33. package/{template/packages → dist}/sdk/package.json +9 -10
  34. package/dist/sdk/src/agentContext.ts +36 -0
  35. package/dist/sdk/src/bundleModule.ts +175 -0
  36. package/dist/sdk/src/components/Dialog.tsx +103 -0
  37. package/dist/sdk/src/components/DocumentEditor.tsx +113 -0
  38. package/dist/sdk/src/components/DocumentPage.tsx +184 -0
  39. package/{template/packages → dist}/sdk/src/components/Markdown.tsx +9 -3
  40. package/{template/packages → dist}/sdk/src/components/MarkdownEditor.tsx +26 -5
  41. package/{template/packages → dist}/sdk/src/components/MultiSelectActionBar.tsx +95 -12
  42. package/{template/packages → dist}/sdk/src/components/MultiSelectCheckbox.tsx +4 -2
  43. package/dist/sdk/src/components/NotisCommentBoundary.tsx +172 -0
  44. package/{template/packages → dist}/sdk/src/components/NotisSelectionBoundary.tsx +9 -5
  45. package/dist/sdk/src/components/RecordComponents.tsx +111 -0
  46. package/dist/sdk/src/components/Skeleton.tsx +38 -0
  47. package/dist/sdk/src/config.ts +31 -0
  48. package/{template/packages → dist}/sdk/src/documents.ts +21 -6
  49. package/dist/sdk/src/hooks/useAction.ts +28 -0
  50. package/dist/sdk/src/hooks/useAgentContext.ts +23 -0
  51. package/{template/packages → dist}/sdk/src/hooks/useBackend.ts +3 -2
  52. package/dist/sdk/src/hooks/useCloudComputer.ts +94 -0
  53. package/dist/sdk/src/hooks/useCloudComputerShell.ts +114 -0
  54. package/{template/packages → dist}/sdk/src/hooks/useCollectionInteractions.ts +157 -147
  55. package/dist/sdk/src/hooks/useDatabaseSchema.ts +50 -0
  56. package/{template/packages → dist}/sdk/src/hooks/useDatabaseSubscription.ts +2 -2
  57. package/dist/sdk/src/hooks/useDocument.ts +51 -0
  58. package/{template/packages → dist}/sdk/src/hooks/useDocuments.ts +25 -59
  59. package/{template/packages → dist}/sdk/src/hooks/useHandover.ts +7 -7
  60. package/dist/sdk/src/hooks/useLongPressSelection.ts +79 -0
  61. package/dist/sdk/src/hooks/useMarqueeSelection.ts +194 -0
  62. package/{template/packages → dist}/sdk/src/hooks/useMultiSelect.ts +2 -8
  63. package/dist/sdk/src/hooks/useNativeDocumentBody.ts +50 -0
  64. package/dist/sdk/src/hooks/useNativeDocuments.ts +143 -0
  65. package/{template/packages → dist}/sdk/src/hooks/useNotis.ts +6 -1
  66. package/dist/sdk/src/hooks/useNotisNavigation.ts +70 -0
  67. package/dist/sdk/src/hooks/useQuery.ts +81 -0
  68. package/dist/sdk/src/hooks/useToolQuery.ts +12 -0
  69. package/dist/sdk/src/hooks/useTopBarSearch.ts +102 -0
  70. package/dist/sdk/src/hooks/useTopBarTitleLeaf.ts +75 -0
  71. package/{template/packages → dist}/sdk/src/hooks/useUpsertDocument.ts +1 -1
  72. package/dist/sdk/src/hooks/useViewParams.ts +195 -0
  73. package/dist/sdk/src/hooks/useViewerReads.ts +129 -0
  74. package/{template/packages → dist}/sdk/src/index.ts +64 -1
  75. package/{template/packages → dist}/sdk/src/interactions/actions.ts +14 -1
  76. package/{template/packages → dist}/sdk/src/interactions/shortcuts.tsx +129 -23
  77. package/dist/sdk/src/interactions/visibility.ts +13 -0
  78. package/{template/packages → dist}/sdk/src/interactions.ts +5 -1
  79. package/dist/sdk/src/nativeData.ts +39 -0
  80. package/{template/packages → dist}/sdk/src/provider.tsx +9 -4
  81. package/dist/sdk/src/queryCache.ts +292 -0
  82. package/dist/sdk/src/queryErrors.ts +51 -0
  83. package/dist/sdk/src/runtime.ts +675 -0
  84. package/dist/sdk/src/runtimeModules.ts +33 -0
  85. package/dist/sdk/src/space.ts +442 -0
  86. package/dist/sdk/src/standardViewParams.ts +36 -0
  87. package/{template/packages → dist}/sdk/src/styles.css +81 -16
  88. package/dist/sdk/src/tailwind.ts +66 -0
  89. package/dist/sdk/src/ui/charts/RenderChartData.tsx +25 -0
  90. package/dist/sdk/src/ui/engine/core.ts +392 -0
  91. package/dist/sdk/src/ui/engine/dom.ts +183 -0
  92. package/dist/sdk/src/ui/engine/hooks.ts +195 -0
  93. package/dist/sdk/src/ui/engine/instance.ts +63 -0
  94. package/dist/sdk/src/ui/engine/keycapContext.ts +2 -0
  95. package/dist/sdk/src/ui/engine/registry.ts +262 -0
  96. package/dist/sdk/src/ui/engine/types.ts +62 -0
  97. package/dist/sdk/src/ui/icons.tsx +91 -0
  98. package/dist/sdk/src/ui/util.ts +83 -0
  99. package/dist/sdk/src/viewPeriod.ts +26 -0
  100. package/dist/sdk/src/vite.ts +92 -0
  101. package/dist/sdk/src/words/actions.ts +295 -0
  102. package/dist/sdk/src/words/clashes.ts +158 -0
  103. package/dist/sdk/src/words/format.ts +136 -0
  104. package/dist/sdk/src/words/index.ts +13 -0
  105. package/dist/sdk/src/words/keymap.ts +72 -0
  106. package/dist/sdk/src/words/keys.ts +297 -0
  107. package/dist/sdk/src/words/shell.ts +46 -0
  108. package/dist/sdk/src/words/types.ts +65 -0
  109. package/dist/skill-sync/index.js +832 -151
  110. package/dist/skill-sync/index.js.map +4 -4
  111. package/dist/skill-sync-worker.mjs +3941 -0
  112. package/dist/space-harness/host.js +1460 -0
  113. package/dist/space-sdk/dist/chunk-ZSN57ATH.js +5 -0
  114. package/dist/space-sdk/dist/index.js +15 -0
  115. package/dist/space-sdk/dist/ui.js +18 -0
  116. package/dist/space-sdk/package.json +33 -0
  117. package/dist/space-sdk/src/agentContext.d.ts +36 -0
  118. package/dist/space-sdk/src/components/Dialog.d.ts +20 -0
  119. package/dist/space-sdk/src/components/DocumentEditor.d.ts +46 -0
  120. package/dist/space-sdk/src/components/DocumentPage.d.ts +53 -0
  121. package/dist/space-sdk/src/components/Markdown.d.ts +18 -0
  122. package/dist/space-sdk/src/components/MarkdownEditor.d.ts +11 -0
  123. package/dist/space-sdk/src/components/MultiSelectActionBar.d.ts +31 -0
  124. package/dist/space-sdk/src/components/MultiSelectCheckbox.d.ts +14 -0
  125. package/dist/space-sdk/src/components/MultiSelectDragOverlay.d.ts +8 -0
  126. package/dist/space-sdk/src/components/NotisCommentBoundary.d.ts +36 -0
  127. package/dist/space-sdk/src/components/NotisSelectionBoundary.d.ts +16 -0
  128. package/dist/space-sdk/src/components/RecordComponents.d.ts +64 -0
  129. package/dist/space-sdk/src/components/ShortcutHints.d.ts +12 -0
  130. package/dist/space-sdk/src/components/Skeleton.d.ts +23 -0
  131. package/dist/space-sdk/src/config.ts +31 -0
  132. package/dist/space-sdk/src/documents.d.ts +59 -0
  133. package/dist/space-sdk/src/hooks/useAction.d.ts +8 -0
  134. package/dist/space-sdk/src/hooks/useActiveResource.d.ts +3 -0
  135. package/dist/space-sdk/src/hooks/useAgentContext.d.ts +7 -0
  136. package/dist/space-sdk/src/hooks/useBackend.d.ts +23 -0
  137. package/dist/space-sdk/src/hooks/useCloudComputer.d.ts +45 -0
  138. package/dist/space-sdk/src/hooks/useCloudComputerShell.d.ts +49 -0
  139. package/dist/space-sdk/src/hooks/useCollectionInteractions.d.ts +118 -0
  140. package/dist/space-sdk/src/hooks/useDatabaseSchema.d.ts +14 -0
  141. package/dist/space-sdk/src/hooks/useDatabaseSubscription.d.ts +26 -0
  142. package/dist/space-sdk/src/hooks/useDocument.d.ts +22 -0
  143. package/dist/space-sdk/src/hooks/useDocuments.d.ts +31 -0
  144. package/dist/space-sdk/src/hooks/useHandover.d.ts +43 -0
  145. package/dist/space-sdk/src/hooks/useLongPressSelection.d.ts +9 -0
  146. package/dist/space-sdk/src/hooks/useMarqueeSelection.d.ts +57 -0
  147. package/dist/space-sdk/src/hooks/useMultiSelect.d.ts +51 -0
  148. package/dist/space-sdk/src/hooks/useNativeDocumentBody.d.ts +24 -0
  149. package/dist/space-sdk/src/hooks/useNativeDocuments.d.ts +55 -0
  150. package/dist/space-sdk/src/hooks/useNotis.d.ts +25 -0
  151. package/dist/space-sdk/src/hooks/useNotisNavigation.d.ts +33 -0
  152. package/dist/space-sdk/src/hooks/useQuery.d.ts +22 -0
  153. package/dist/space-sdk/src/hooks/useTool.d.ts +20 -0
  154. package/dist/space-sdk/src/hooks/useToolQuery.d.ts +3 -0
  155. package/dist/space-sdk/src/hooks/useTools.d.ts +15 -0
  156. package/dist/space-sdk/src/hooks/useTopBarSearch.d.ts +48 -0
  157. package/dist/space-sdk/src/hooks/useTopBarTitleLeaf.d.ts +24 -0
  158. package/dist/space-sdk/src/hooks/useUpsertDocument.d.ts +27 -0
  159. package/dist/space-sdk/src/hooks/useViewParams.d.ts +76 -0
  160. package/dist/space-sdk/src/hooks/useViewerReads.d.ts +38 -0
  161. package/dist/space-sdk/src/index.d.ts +98 -0
  162. package/dist/space-sdk/src/interactions/actions.d.ts +33 -0
  163. package/dist/space-sdk/src/interactions/shortcuts.d.ts +42 -0
  164. package/dist/space-sdk/src/interactions/visibility.d.ts +2 -0
  165. package/dist/space-sdk/src/nativeData.d.ts +66 -0
  166. package/dist/space-sdk/src/provider.d.ts +17 -0
  167. package/dist/space-sdk/src/queryCache.d.ts +45 -0
  168. package/dist/space-sdk/src/queryErrors.ts +51 -0
  169. package/dist/space-sdk/src/runtime.d.ts +615 -0
  170. package/dist/space-sdk/src/runtimeModules.ts +33 -0
  171. package/dist/space-sdk/src/space.ts +442 -0
  172. package/dist/space-sdk/src/standardViewParams.d.ts +15 -0
  173. package/dist/space-sdk/src/tailwind-candidates.d.ts +3418 -0
  174. package/dist/space-sdk/src/tailwind.ts +66 -0
  175. package/dist/space-sdk/src/ui/actions/ActionToast.d.ts +15 -0
  176. package/dist/space-sdk/src/ui/actions/actionWords.d.ts +16 -0
  177. package/dist/space-sdk/src/ui/actions/index.d.ts +7 -0
  178. package/dist/space-sdk/src/ui/actions/useActions.d.ts +69 -0
  179. package/dist/space-sdk/src/ui/actions/useToast.d.ts +41 -0
  180. package/dist/space-sdk/src/ui/blocks/Tabs.d.ts +139 -0
  181. package/dist/space-sdk/src/ui/blocks/blocks.d.ts +217 -0
  182. package/dist/space-sdk/src/ui/blocks/index.d.ts +4 -0
  183. package/dist/space-sdk/src/ui/board/BoardView.d.ts +36 -0
  184. package/dist/space-sdk/src/ui/board/boardModel.d.ts +46 -0
  185. package/dist/space-sdk/src/ui/board/boardWords.d.ts +9 -0
  186. package/dist/space-sdk/src/ui/board/index.d.ts +5 -0
  187. package/dist/space-sdk/src/ui/calendar/CalendarView.d.ts +49 -0
  188. package/dist/space-sdk/src/ui/calendar/DayPicker.d.ts +23 -0
  189. package/dist/space-sdk/src/ui/calendar/calendarMath.d.ts +81 -0
  190. package/dist/space-sdk/src/ui/charts/Chart.d.ts +203 -0
  191. package/dist/space-sdk/src/ui/charts/ChartTable.d.ts +37 -0
  192. package/dist/space-sdk/src/ui/charts/RenderChartData.d.ts +16 -0
  193. package/dist/space-sdk/src/ui/charts/StatRow.d.ts +59 -0
  194. package/dist/space-sdk/src/ui/charts/chartData.d.ts +84 -0
  195. package/dist/space-sdk/src/ui/comments/CommentPane.d.ts +58 -0
  196. package/dist/space-sdk/src/ui/comments/commentModel.d.ts +157 -0
  197. package/dist/space-sdk/src/ui/comments/commentWords.d.ts +60 -0
  198. package/dist/space-sdk/src/ui/comments/index.d.ts +4 -0
  199. package/dist/space-sdk/src/ui/comments/useComments.d.ts +146 -0
  200. package/dist/space-sdk/src/ui/engine/HelpSheet.d.ts +12 -0
  201. package/dist/space-sdk/src/ui/engine/KeyboardEngine.d.ts +27 -0
  202. package/dist/space-sdk/src/ui/engine/core.d.ts +73 -0
  203. package/dist/space-sdk/src/ui/engine/hooks.d.ts +64 -0
  204. package/dist/space-sdk/src/ui/engine/index.d.ts +14 -0
  205. package/dist/space-sdk/src/ui/engine/instance.d.ts +21 -0
  206. package/dist/space-sdk/src/ui/engine/keycapContext.d.ts +2 -0
  207. package/dist/space-sdk/src/ui/engine/registry.d.ts +77 -0
  208. package/dist/space-sdk/src/ui/engine/step.d.ts +11 -0
  209. package/dist/space-sdk/src/ui/engine/types.d.ts +54 -0
  210. package/dist/space-sdk/src/ui/fields/fieldWords.d.ts +56 -0
  211. package/dist/space-sdk/src/ui/fields/fields.d.ts +162 -0
  212. package/dist/space-sdk/src/ui/fields/index.d.ts +5 -0
  213. package/dist/space-sdk/src/ui/fields/useFieldDraft.d.ts +22 -0
  214. package/dist/space-sdk/src/ui/gallery/AttachmentGallery.d.ts +53 -0
  215. package/dist/space-sdk/src/ui/gallery/ImageViewer.d.ts +27 -0
  216. package/dist/space-sdk/src/ui/gallery/galleryWords.d.ts +95 -0
  217. package/dist/space-sdk/src/ui/gallery/index.d.ts +5 -0
  218. package/dist/space-sdk/src/ui/icons.d.ts +13 -0
  219. package/dist/space-sdk/src/ui/index.d.ts +54 -0
  220. package/dist/space-sdk/src/ui/keycap/Keycap.d.ts +22 -0
  221. package/dist/space-sdk/src/ui/keycap/index.d.ts +2 -0
  222. package/dist/space-sdk/src/ui/layout/Panels.d.ts +50 -0
  223. package/dist/space-sdk/src/ui/layout/chatPanelEdge.d.ts +77 -0
  224. package/dist/space-sdk/src/ui/layout/index.d.ts +5 -0
  225. package/dist/space-sdk/src/ui/layout/panelMath.d.ts +91 -0
  226. package/dist/space-sdk/src/ui/list/ListReader.d.ts +380 -0
  227. package/dist/space-sdk/src/ui/list/Row.d.ts +145 -0
  228. package/dist/space-sdk/src/ui/list/SelectionBar.d.ts +65 -0
  229. package/dist/space-sdk/src/ui/list/index.d.ts +14 -0
  230. package/dist/space-sdk/src/ui/list/rowState.d.ts +22 -0
  231. package/dist/space-sdk/src/ui/list/useListKeys.d.ts +89 -0
  232. package/dist/space-sdk/src/ui/list/useReorder.d.ts +86 -0
  233. package/dist/space-sdk/src/ui/list/useSelection.d.ts +90 -0
  234. package/dist/space-sdk/src/ui/open/OpenAsMenu.d.ts +15 -0
  235. package/dist/space-sdk/src/ui/open/index.d.ts +6 -0
  236. package/dist/space-sdk/src/ui/open/openModeStore.d.ts +39 -0
  237. package/dist/space-sdk/src/ui/open/useOpenMode.d.ts +17 -0
  238. package/dist/space-sdk/src/ui/overlay/Menu.d.ts +88 -0
  239. package/dist/space-sdk/src/ui/overlay/Popover.d.ts +28 -0
  240. package/dist/space-sdk/src/ui/overlay/Tooltip.d.ts +19 -0
  241. package/dist/space-sdk/src/ui/overlay/index.d.ts +9 -0
  242. package/dist/space-sdk/src/ui/overlay/layerRoot.d.ts +24 -0
  243. package/dist/space-sdk/src/ui/overlay/position.d.ts +31 -0
  244. package/dist/space-sdk/src/ui/period/PeriodPicker.d.ts +30 -0
  245. package/dist/space-sdk/src/ui/period/dateFormat.d.ts +50 -0
  246. package/dist/space-sdk/src/ui/period/dateWords.d.ts +88 -0
  247. package/dist/space-sdk/src/ui/period/periods.d.ts +70 -0
  248. package/dist/space-sdk/src/ui/period/usePeriod.d.ts +23 -0
  249. package/dist/space-sdk/src/ui/present/index.d.ts +3 -0
  250. package/dist/space-sdk/src/ui/present/presentWords.d.ts +27 -0
  251. package/dist/space-sdk/src/ui/present/screens.d.ts +36 -0
  252. package/dist/space-sdk/src/ui/present/useAudienceScreen.d.ts +38 -0
  253. package/dist/space-sdk/src/ui/styles.d.ts +21 -0
  254. package/dist/space-sdk/src/ui/timeline/TimelineView.d.ts +44 -0
  255. package/dist/space-sdk/src/ui/timeline/timelineMath.d.ts +123 -0
  256. package/dist/space-sdk/src/viewPeriod.d.ts +8 -0
  257. package/dist/space-sdk/src/vite.ts +92 -0
  258. package/dist/space-sdk/src/words/actions.ts +295 -0
  259. package/dist/space-sdk/src/words/clashes.ts +158 -0
  260. package/dist/space-sdk/src/words/format.ts +136 -0
  261. package/dist/space-sdk/src/words/index.ts +13 -0
  262. package/dist/space-sdk/src/words/keymap.ts +72 -0
  263. package/dist/space-sdk/src/words/keys.ts +297 -0
  264. package/dist/space-sdk/src/words/shell.ts +46 -0
  265. package/dist/space-sdk/src/words/types.ts +65 -0
  266. package/dist/view-renderer/dist/host.css +1 -0
  267. package/dist/view-renderer/dist/host.js +2374 -0
  268. package/dist/view-renderer/dist/theme.css +73 -0
  269. package/dist/view-renderer/src/dom.js +212 -0
  270. package/dist/view-renderer/src/errors.js +8 -0
  271. package/dist/view-renderer/src/host.tsx +250 -0
  272. package/dist/view-renderer/src/index.js +4 -0
  273. package/dist/view-renderer/src/png.js +213 -0
  274. package/dist/view-renderer/src/pool.js +110 -0
  275. package/dist/view-renderer/src/remote-broker.js +59 -0
  276. package/dist/view-renderer/src/renderer.js +271 -0
  277. package/package.json +21 -11
  278. package/skills/notis-cli/AGENT_INSTRUCTIONS.md +63 -12
  279. package/skills/notis-onboarding/BRIEF.md +14 -9
  280. package/skills/notis-query/cli.md +1 -1
  281. package/skills/notis-spaces/cli.md +540 -0
  282. package/src/cli.js +8 -8
  283. package/src/command-specs/agents.js +121 -8
  284. package/src/command-specs/auth.js +3 -3
  285. package/src/command-specs/diagnostics.js +97 -20
  286. package/src/command-specs/documents.js +158 -0
  287. package/src/command-specs/files.js +29 -0
  288. package/src/command-specs/handover.js +331 -79
  289. package/src/command-specs/helpers.js +50 -40
  290. package/src/command-specs/index.js +14 -5
  291. package/src/command-specs/meta.js +38 -9
  292. package/src/command-specs/onboarding.js +1 -1
  293. package/src/command-specs/profile.js +42 -1
  294. package/src/command-specs/skills.js +314 -9
  295. package/src/command-specs/smoke.js +16 -4
  296. package/src/command-specs/spaces.js +783 -0
  297. package/src/command-specs/tools.js +118 -21
  298. package/src/command-specs/version-history.js +20 -0
  299. package/src/command-specs/views.js +69 -0
  300. package/src/runtime/agent-browser.js +178 -267
  301. package/src/runtime/agent-setup.js +172 -13
  302. package/src/runtime/app-boundary-validator.js +338 -2
  303. package/src/runtime/app-data-literals.js +82 -0
  304. package/src/runtime/app-platform.js +750 -1568
  305. package/src/runtime/auth-recovery.js +17 -0
  306. package/src/runtime/base-skills.d.ts +11 -0
  307. package/src/runtime/base-skills.js +100 -3
  308. package/src/runtime/computer-identity.d.ts +30 -0
  309. package/src/runtime/computer-identity.js +151 -0
  310. package/src/runtime/config-lock.js +135 -0
  311. package/src/runtime/confined-space-build.js +74 -0
  312. package/src/runtime/confined-space-verify.js +49 -0
  313. package/src/runtime/delegated-context.js +5 -21
  314. package/src/runtime/native-record-files.js +47 -0
  315. package/src/runtime/oauth.js +342 -31
  316. package/src/runtime/profiles.js +19 -112
  317. package/src/runtime/retired-sdk-files.js +24 -0
  318. package/src/runtime/retry.js +150 -0
  319. package/src/runtime/skill-sync/cloud-client.ts +33 -8
  320. package/src/runtime/skill-sync/index.ts +443 -106
  321. package/src/runtime/skill-sync/local-scanner.ts +248 -32
  322. package/src/runtime/skill-sync/native-identity.ts +102 -0
  323. package/src/runtime/skill-sync/native-plan.ts +70 -0
  324. package/src/runtime/skill-sync/symlink-manager.ts +66 -16
  325. package/src/runtime/skill-sync/sync-plan.ts +34 -2
  326. package/src/runtime/skill-sync/types.ts +54 -0
  327. package/src/runtime/skill-sync/write-cloud-skill.ts +4 -4
  328. package/src/runtime/skill-sync-service.js +109 -0
  329. package/src/runtime/source-root.js +32 -0
  330. package/src/runtime/space-collection-scaffold.js +107 -0
  331. package/src/runtime/space-database-structures.js +229 -0
  332. package/src/runtime/space-fixture-broker.js +68 -0
  333. package/src/runtime/space-lock.js +337 -0
  334. package/src/runtime/space-platform.js +133 -0
  335. package/src/runtime/space-source.js +290 -0
  336. package/src/runtime/space-test-server.js +354 -0
  337. package/src/runtime/space-verification.js +94 -0
  338. package/src/runtime/space-verify-browser-worker.js +51 -0
  339. package/src/runtime/space-view-manifest.js +368 -0
  340. package/src/runtime/sync-skills.d.ts +5 -1
  341. package/src/runtime/sync-skills.js +11 -2
  342. package/src/runtime/tool-confirmation.js +138 -0
  343. package/src/runtime/transport.js +118 -24
  344. package/src/runtime/upsell.js +219 -0
  345. package/src/runtime/view-render.js +49 -0
  346. package/src/skill-sync-worker-entry.js +2 -0
  347. package/src/skill-sync-worker.js +114 -0
  348. package/src/space-harness-host.ts +122 -0
  349. package/template/.harness/index.html.tmpl +9 -352
  350. package/template/app/globals.css +29 -3
  351. package/template/app/layout.tsx +2 -6
  352. package/template/components/ui/badge.tsx +7 -4
  353. package/template/components/ui/button.tsx +1 -1
  354. package/template/components/ui/native-select.tsx +24 -0
  355. package/template/notis.config.ts +4 -36
  356. package/template/package-lock.json +1800 -1867
  357. package/template/package.json +16 -14
  358. package/template/postcss.config.mjs +1 -1
  359. package/template/tailwind.config.ts +1 -6
  360. package/template/tsconfig.json +1 -0
  361. package/template/vite.config.ts +2 -2
  362. package/dist/base-skills/notis-apps/SKILL.md +0 -613
  363. package/skills/notis-apps/cli.md +0 -285
  364. package/src/command-specs/apps.js +0 -3000
  365. package/src/runtime/app-changelog.js +0 -79
  366. package/src/runtime/app-dev-consumers.js +0 -154
  367. package/src/runtime/app-dev-host-lock.js +0 -80
  368. package/src/runtime/app-dev-process-identity.js +0 -111
  369. package/src/runtime/app-dev-roots.js +0 -284
  370. package/src/runtime/app-dev-server.js +0 -1140
  371. package/src/runtime/app-dev-sessions.js +0 -185
  372. package/src/runtime/app-registry-scaffolds.js +0 -367
  373. package/src/runtime/assets/store-screenshot-dark.png +0 -0
  374. package/src/runtime/cli-mode.generated.js +0 -5
  375. package/src/runtime/cli-mode.js +0 -34
  376. package/src/runtime/store-screenshot.js +0 -143
  377. package/template/CHANGELOG.md +0 -5
  378. package/template/app/page.tsx +0 -60
  379. package/template/components/ui/card.tsx +0 -56
  380. package/template/packages/sdk/src/components/DocumentEditor.tsx +0 -103
  381. package/template/packages/sdk/src/config.ts +0 -259
  382. package/template/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
  383. package/template/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
  384. package/template/packages/sdk/src/hooks/useDocument.ts +0 -78
  385. package/template/packages/sdk/src/hooks/useNotisNavigation.ts +0 -52
  386. package/template/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
  387. package/template/packages/sdk/src/runtime.ts +0 -439
  388. package/template/packages/sdk/src/vite.ts +0 -56
  389. package/template/packages/sdk/tsconfig.json +0 -15
  390. /package/{template/packages → dist}/sdk/src/components/MultiSelectDragOverlay.tsx +0 -0
  391. /package/{template/packages → dist}/sdk/src/components/ShortcutHints.tsx +0 -0
  392. /package/{template/packages → dist}/sdk/src/hooks/useActiveResource.ts +0 -0
  393. /package/{template/packages → dist}/sdk/src/hooks/useTool.ts +0 -0
  394. /package/{template/packages → dist}/sdk/src/hooks/useTools.ts +0 -0
  395. /package/{template/packages → dist}/sdk/src/ui.ts +0 -0
@@ -0,0 +1,129 @@
1
+ 'use client';
2
+
3
+ import { useNotisRuntime } from '../provider';
4
+ import { useQuery, type UseQueryResult } from './useQuery';
5
+ import {
6
+ SPACE_VIEWER_READ_OPERATIONS,
7
+ type SpaceViewerDatabase,
8
+ type SpaceViewerReadFamily,
9
+ type SpaceViewerReadInput,
10
+ type SpaceViewerReadOperation,
11
+ type SpaceViewerReadResult,
12
+ type SpaceViewerSkill,
13
+ } from '../space';
14
+
15
+ export interface UseViewerReadOptions {
16
+ enabled?: boolean;
17
+ staleTimeMs?: number;
18
+ }
19
+
20
+ // Bounded crawl of the viewer's Skill inventory: 50 pages of 100.
21
+ const MAX_SKILL_PAGES = 50;
22
+
23
+ /**
24
+ * True when this page may read what the signed-in viewer can open: the Space declares
25
+ * `viewerReads` for this family and the host offers it (never on a Site or anonymous link).
26
+ * Render a fallback when false.
27
+ */
28
+ export function useViewerReadAvailable(family: SpaceViewerReadFamily): boolean {
29
+ const runtime = useNotisRuntime();
30
+ return runtime?.resource?.kind === 'space' && typeof runtime.viewerRead === 'function'
31
+ && Boolean(runtime.space?.viewerReads?.includes(family));
32
+ }
33
+
34
+ /** One cached, read-only viewer read. Results are scoped to the current viewer by the host cache scope. */
35
+ export function useViewerRead<TOperation extends SpaceViewerReadOperation>(operation: TOperation,
36
+ input: SpaceViewerReadInput[TOperation], options: UseViewerReadOptions = {}): UseQueryResult<SpaceViewerReadResult[TOperation]> {
37
+ const runtime = useNotisRuntime();
38
+ const available = useViewerReadAvailable(SPACE_VIEWER_READ_OPERATIONS[operation]);
39
+ return useQuery(['viewer-read', operation, input], async () => {
40
+ if (!runtime?.viewerRead) throw new Error('Viewer reads are unavailable here.');
41
+ return runtime.viewerRead<SpaceViewerReadResult[TOperation]>(operation, input as Record<string, unknown>);
42
+ }, { readOnly: true, enabled: available && options.enabled !== false, staleTimeMs: options.staleTimeMs });
43
+ }
44
+
45
+ /** Every database the signed-in viewer can open, with its available links and read target. */
46
+ export function useViewerDatabases(options: UseViewerReadOptions = {}): UseQueryResult<SpaceViewerDatabase[]> {
47
+ const runtime = useNotisRuntime();
48
+ const available = useViewerReadAvailable('databases');
49
+ return useQuery(['viewer-databases'], async () => {
50
+ if (!runtime?.viewerRead) throw new Error('Viewer reads are unavailable here.');
51
+ const result = await runtime.viewerRead<SpaceViewerReadResult['list_databases']>('list_databases', {});
52
+ if (!Array.isArray(result?.databases)) throw new Error('The database list is incomplete.');
53
+ return result.databases;
54
+ }, { readOnly: true, enabled: available && options.enabled !== false, staleTimeMs: options.staleTimeMs });
55
+ }
56
+
57
+ type ListSkillsRead = (input: SpaceViewerReadInput['list_skills']) => Promise<SpaceViewerReadResult['list_skills']>;
58
+
59
+ // Content pages read at once. Each one holds an API worker while its snapshots are read, and the API
60
+ // has only a few, so a large inventory never takes them all: two pages, plus one quick cursor read.
61
+ export const SKILL_CONTENT_PAGES_AT_ONCE = 2;
62
+
63
+ /**
64
+ * Every Skill page, in order. Pages with instructions are slow (each reads its snapshots), so the next
65
+ * page's cursor comes from a quick read without them and that page starts while the previous one is
66
+ * still read, at most SKILL_CONTENT_PAGES_AT_ONCE content pages at a time. A cursor that no longer
67
+ * matches (the list changed meanwhile) drops the pages started from it, waits for them to end and
68
+ * continues from the real cursor.
69
+ */
70
+ export async function readAllViewerSkills(read: ListSkillsRead,
71
+ { includeContent, includeDisabled }: { includeContent: boolean; includeDisabled: boolean }): Promise<SpaceViewerSkill[]> {
72
+ const page = (after: string | null, content: boolean) => read({ limit: 100, include_content: content,
73
+ include_disabled: includeDisabled, ...(after ? { after } : {}) }).then(result => {
74
+ if (!Array.isArray(result?.skills)) throw new Error('The Skill list is incomplete.');
75
+ return result;
76
+ });
77
+ const skills: SpaceViewerSkill[] = [];
78
+ // The pages started and not read yet, in order, each with the cursor it started from.
79
+ const started: { after: string | null; result: Promise<SpaceViewerReadResult['list_skills']> }[] = [];
80
+ // Where the next page to start begins, and whether a quick read (or the last page read) says it exists.
81
+ let cursor: string | null = null;
82
+ let known = true;
83
+ let pages = 0;
84
+ for (;;) {
85
+ if (known && started.length < (includeContent ? SKILL_CONTENT_PAGES_AT_ONCE : 1) && pages + started.length < MAX_SKILL_PAGES) {
86
+ const current = { after: cursor, result: page(cursor, includeContent) };
87
+ void current.result.catch(() => {});
88
+ started.push(current);
89
+ known = false;
90
+ if (includeContent) {
91
+ const probe = await page(cursor, false);
92
+ known = Boolean(probe.has_more && probe.next);
93
+ cursor = probe.next ?? null;
94
+ }
95
+ continue;
96
+ }
97
+ const head = started.shift();
98
+ if (!head) break;
99
+ const result = await head.result;
100
+ skills.push(...result.skills);
101
+ pages += 1;
102
+ if (!result.has_more || !result.next) return skills;
103
+ // A page started ahead is kept only when it continues exactly where this one ended; otherwise (and
104
+ // when none was started) the next page starts from this page's own cursor.
105
+ if (started[0]?.after !== result.next) {
106
+ const dropped = started.splice(0);
107
+ await Promise.allSettled(dropped.map(entry => entry.result));
108
+ cursor = result.next;
109
+ known = true;
110
+ }
111
+ }
112
+ throw new Error('This account has more Skills than a page can read at once.');
113
+ }
114
+
115
+ /**
116
+ * Every Skill the signed-in viewer can see, all pages. With `includeContent`, `skill_md`
117
+ * is present where the viewer holds content access and null elsewhere (curated, legacy).
118
+ */
119
+ export function useViewerSkills(options: UseViewerReadOptions & { includeContent?: boolean; includeDisabled?: boolean } = {}):
120
+ UseQueryResult<SpaceViewerSkill[]> {
121
+ const runtime = useNotisRuntime();
122
+ const available = useViewerReadAvailable('skills');
123
+ const includeContent = options.includeContent === true, includeDisabled = options.includeDisabled === true;
124
+ return useQuery(['viewer-skills', includeContent, includeDisabled], async () => {
125
+ if (!runtime?.viewerRead) throw new Error('Viewer reads are unavailable here.');
126
+ return readAllViewerSkills((input: SpaceViewerReadInput['list_skills']) => runtime.viewerRead!('list_skills', input),
127
+ { includeContent, includeDisabled });
128
+ }, { readOnly: true, enabled: available && options.enabled !== false, staleTimeMs: options.staleTimeMs });
129
+ }
@@ -10,6 +10,32 @@
10
10
  export { NotisProvider, useNotisRuntime } from './provider';
11
11
 
12
12
  // Hooks
13
+ export { useAction, useActionQuery } from './hooks/useAction';
14
+ export { useNativeDocuments, useNativeMutation, useNativeDelete, type NativeDeleteManyResult } from './hooks/useNativeDocuments';
15
+ export type { NativeField, NativeFilter, NativeQuery, NativeInsert, NativeUpdate, NativeDelete, NativeMutationReceipt } from './nativeData';
16
+ export type { SpaceDatabaseCreate, SpaceResourceReference } from './space';
17
+ export type { SpaceActionTemplate, SpaceActionDescriptor, SpaceRuntimeDescriptor, SpaceActionOptions } from './space';
18
+ export type { SpaceDocumentBodyRequest, SpaceDocumentBodyRead, SpaceDocumentBodySave, SpaceDocumentBodyResult } from './space';
19
+ export { useNativeDocumentBody } from './hooks/useNativeDocumentBody';
20
+ export { useViewerRead, useViewerReadAvailable, useViewerDatabases, useViewerSkills } from './hooks/useViewerReads';
21
+ export type { UseViewerReadOptions } from './hooks/useViewerReads';
22
+ export { useViewParams, useShown, useShownAvailable, parseViewParams } from './hooks/useViewParams';
23
+ export type { ViewParams, ViewParamsOf, ViewParamChanges, SetViewParamsOptions, UseShownOptions } from './hooks/useViewParams';
24
+ export { SPACE_VIEW_SPEC_VERSION } from './space';
25
+ export type { SpaceViewParam, SpaceViewParamType, SpaceViewFilter, SpaceShows, SpaceShownDescriptor, SpaceShownOptions, SpaceShownResult,
26
+ SpaceMemoryPolicy, SpaceMemorySnapshot, SpaceChrome, SpaceMarkdownInput } from './space';
27
+ export { SPACE_VIEWER_READ_OPERATIONS } from './space';
28
+ export { SPACE_CLOUD_COMPUTER_OPERATIONS, spaceCloudComputerAllows } from './space';
29
+ export type { SpaceCloudComputerLevel, SpaceCloudComputerOperation, SpaceCloudComputerStatus, SpaceCloudComputerRelease, SpaceCloudComputerRunInput,
30
+ SpaceCloudComputerRunResult, SpaceCloudComputerUploadInput, SpaceCloudComputerUploadResult, SpaceCloudComputerTransport } from './space';
31
+ export type { SpaceViewerReadFamily, SpaceViewerReadOperation, SpaceViewerReadInput, SpaceViewerReadResult, SpaceViewerLink,
32
+ SpaceViewerDatabase, SpaceViewerDatabaseSchema, SpaceViewerSkill, SpaceViewerSkillPage } from './space';
33
+ export { SpaceActionError, SpaceNavigationCancelled, isSpaceNavigationCancelled } from './space';
34
+ export type { SpaceActionFailureDetails } from './space';
35
+ export { useQuery, useQueryClient } from './hooks/useQuery';
36
+ export type { UseQueryOptions, UseQueryResult } from './hooks/useQuery';
37
+ export { createQueryClient, createPrefetchQueue, queryKey } from './queryCache';
38
+ export type { NotisQueryClient, QuerySnapshot, QueryFetchOptions, HiddenRecords } from './queryCache';
13
39
  export { useNotis } from './hooks/useNotis';
14
40
  export { useDocuments } from './hooks/useDocuments';
15
41
  export type { UseDocumentsOptions, UseDocumentsResult } from './hooks/useDocuments';
@@ -31,14 +57,18 @@ export { useHandover } from './hooks/useHandover';
31
57
  export type { UseHandoverResult } from './hooks/useHandover';
32
58
  export { useCloudComputer } from './hooks/useCloudComputer';
33
59
  export type { UseCloudComputerResult } from './hooks/useCloudComputer';
60
+ export { useCloudComputerShell } from './hooks/useCloudComputerShell';
61
+ export type { UseCloudComputerShellResult } from './hooks/useCloudComputerShell';
34
62
  export { useNotisNavigation } from './hooks/useNotisNavigation';
35
63
  export { useTopBarSearch } from './hooks/useTopBarSearch';
64
+ export { useTopBarTitleLeaf } from './hooks/useTopBarTitleLeaf';
36
65
  export { useBackend } from './hooks/useBackend';
37
66
  export { useMultiSelect } from './hooks/useMultiSelect';
38
67
  export { useCollectionInteractions, COLLECTION_ITEM_ATTRIBUTE } from './hooks/useCollectionInteractions';
39
68
  export { useActiveResource } from './hooks/useActiveResource';
69
+ // ShortcutProvider is not exported here (D2): NotisProvider mounts it for every app; hosts take it from
70
+ // '@notis/sdk/interactions'.
40
71
  export {
41
- ShortcutProvider,
42
72
  SHORTCUT_SCOPE_PRIORITY,
43
73
  isEditableShortcutEvent,
44
74
  shortcutDisplay,
@@ -49,12 +79,16 @@ export {
49
79
  export { Markdown } from './components/Markdown';
50
80
  export type { MarkdownProps } from './components/Markdown';
51
81
  export { DocumentEditor } from './components/DocumentEditor';
82
+ export { DocumentPage, DocumentPageSkeleton, usePrefetchRecord } from './components/DocumentPage';
83
+ export { RecordProperties, HtmlFrame, ReportFrame, ShareControl, SpacePageFrame } from './components/RecordComponents';
84
+ export { RenderChartData } from './ui/charts/RenderChartData';
52
85
  export { MarkdownEditor } from './components/MarkdownEditor';
53
86
  export { NotisSelectionBoundary, NOTIS_CONTEXT_CLIPBOARD_TYPE } from './components/NotisSelectionBoundary';
54
87
  export type { NotisSelectionBoundaryProps } from './components/NotisSelectionBoundary';
55
88
  export {
56
89
  asRecord,
57
90
  blockNoteToPlainText,
91
+ DOCUMENT_WORDS,
58
92
  extractRichText,
59
93
  getDocumentPreview,
60
94
  getRelationIds,
@@ -66,8 +100,11 @@ export {
66
100
  normalizePropertyValue,
67
101
  optionalString,
68
102
  } from './documents';
103
+ export type { DocumentWordsOptions } from './documents';
69
104
 
70
105
  // Multi-select components
106
+ export { Dialog } from './components/Dialog';
107
+ export type { DialogProps } from './components/Dialog';
71
108
  export { MultiSelectActionBar } from './components/MultiSelectActionBar';
72
109
  export { MultiSelectCheckbox, MultiSelectCheckbox as SelectionCheckbox } from './components/MultiSelectCheckbox';
73
110
  export { MultiSelectDragOverlay, MultiSelectDragOverlay as SelectionMarquee } from './components/MultiSelectDragOverlay';
@@ -129,10 +166,22 @@ export type {
129
166
  HandoverPayload,
130
167
  HandoverResult,
131
168
  NotisDocumentEditorProps,
169
+ NotisDocumentPageProps,
170
+ NotisDocumentPageCrumb,
171
+ NotisDocumentPageMenuItem,
172
+ NotisDocumentPageParts,
173
+ NotisRecordProps,
174
+ NotisRecordPropertiesProps,
175
+ NotisHtmlFrameProps,
176
+ NotisHtmlFramePresentAction,
177
+ NotisReportFrameProps,
178
+ NotisShareControlProps,
179
+ NotisSpacePageFrameProps,
132
180
  NotisMarkdownEditorProps,
133
181
  NotisMarkdownEditorSavePayload,
134
182
  NotisMarkdownEditorSaveResult,
135
183
  NotisRuntime,
184
+ RuntimeResource,
136
185
  NotisRuntimeContext,
137
186
  NotisRuntimeUI,
138
187
  QueryFilter,
@@ -143,3 +192,17 @@ export type {
143
192
  ToolCallOptions,
144
193
  ToolInputSchema,
145
194
  } from './runtime';
195
+
196
+ export { useToolQuery } from './hooks/useToolQuery';
197
+
198
+ export { Skeleton, SKELETON_WORDS, ViewSkeleton } from './components/Skeleton';
199
+
200
+ export { useLongPressSelection } from './hooks/useLongPressSelection';
201
+ export { isInteractionElementVisible } from './interactions/visibility';
202
+
203
+ export type { AgentContextContent, AgentContextItem, ContextAttachment } from './agentContext';
204
+ export { useAgentContext } from './hooks/useAgentContext';
205
+ export { NotisCommentBoundary, NotisCommentBox } from './components/NotisCommentBoundary';
206
+ export type { NotisCommentBoundaryProps, NotisCommentBoxProps } from './components/NotisCommentBoundary';
207
+ export { VIEW_PERIODS, isViewDate, isViewPeriod, resolveViewPeriod } from './viewPeriod';
208
+ export { journalViewParams, tasksViewParams, tasksViewFilter } from './standardViewParams';
@@ -1,6 +1,10 @@
1
1
  import type { ReactNode } from 'react';
2
2
 
3
- export type CollectionActionIntent = 'archive' | 'star' | 'delete' | 'custom';
3
+ export type CollectionActionIntent =
4
+ | 'archive' | 'star' | 'delete'
5
+ | 'enable' | 'disable' | 'pause' | 'resume'
6
+ | 'move' | 'add-to-folder' | 'complete' | 'start-progress'
7
+ | 'custom';
4
8
 
5
9
  export interface CollectionActionContext<T> {
6
10
  selectedIds: string[];
@@ -13,6 +17,7 @@ export interface CollectionAction<T> {
13
17
  intent?: CollectionActionIntent;
14
18
  label?: string;
15
19
  icon?: ReactNode;
20
+ /** Omit to use the intent default, supply a key to override, or false to remove it. */
16
21
  shortcut?: string | false;
17
22
  destructive?: boolean;
18
23
  disabled?: boolean | ((context: CollectionActionContext<T>) => boolean);
@@ -35,6 +40,14 @@ const ACTION_DEFAULTS: Record<Exclude<CollectionActionIntent, 'custom'>, { label
35
40
  archive: { label: 'Archive', shortcut: 'E' },
36
41
  star: { label: 'Star', shortcut: 'S' },
37
42
  delete: { label: 'Delete', shortcut: '#' },
43
+ enable: { label: 'Enable', shortcut: 'E' },
44
+ disable: { label: 'Disable', shortcut: 'D' },
45
+ pause: { label: 'Pause', shortcut: 'P' },
46
+ resume: { label: 'Resume', shortcut: 'R' },
47
+ move: { label: 'Move', shortcut: 'M' },
48
+ 'add-to-folder': { label: 'Add to folder', shortcut: 'F' },
49
+ complete: { label: 'Mark complete', shortcut: 'C' },
50
+ 'start-progress': { label: 'Start progress', shortcut: 'P' },
38
51
  };
39
52
 
40
53
  export function collectionActionDefaults(intent: CollectionActionIntent | undefined): {
@@ -5,11 +5,13 @@ import React, {
5
5
  useCallback,
6
6
  useContext,
7
7
  useEffect,
8
+ useLayoutEffect,
8
9
  useMemo,
9
10
  useRef,
10
11
  useState,
11
12
  type ReactNode,
12
13
  } from 'react';
14
+ import { isInteractionElementVisible } from './visibility';
13
15
 
14
16
  export type ShortcutScope = 'app' | 'route' | 'collection' | 'detail' | 'modal';
15
17
 
@@ -42,6 +44,8 @@ export interface UseShortcutsOptions {
42
44
  sequenceTimeoutMs?: number;
43
45
  /** Internal owner used to route collection shortcuts to the last interacted collection. */
44
46
  collectionOwnerId?: string;
47
+ /** Evaluated at dispatch time, including for retained hidden app views. */
48
+ isAvailable?: () => boolean;
45
49
  }
46
50
 
47
51
  interface ShortcutRegistration {
@@ -52,6 +56,8 @@ interface ShortcutRegistration {
52
56
  enabled: boolean;
53
57
  sequenceTimeoutMs: number;
54
58
  collectionOwnerId?: string;
59
+ /** Evaluated at dispatch time, including for retained hidden app views. */
60
+ isAvailable?: () => boolean;
55
61
  getShortcuts: () => ShortcutDefinition[];
56
62
  }
57
63
 
@@ -92,6 +98,11 @@ export function createShortcutCollectionOwner(): string {
92
98
  return `collection-${state.nextOwnerId++}`;
93
99
  }
94
100
 
101
+ export function releaseShortcutCollection(ownerId: string): void {
102
+ const state = getShortcutCollectionState();
103
+ if (state.activeOwnerId === ownerId) state.activeOwnerId = null;
104
+ }
105
+
95
106
  export function activateShortcutCollection(ownerId: string): void {
96
107
  getShortcutCollectionState().activeOwnerId = ownerId;
97
108
  }
@@ -186,7 +197,8 @@ export function isEditableShortcutEvent(event: Event): boolean {
186
197
  return true;
187
198
  }
188
199
  if (tag === 'A' && target.hasAttribute('href')) return true;
189
- return ['button', 'link', 'menuitem', 'switch', 'tab'].includes(
200
+ // A focused separator (a resizable edge) owns its arrow keys.
201
+ return ['button', 'link', 'menuitem', 'separator', 'switch', 'tab'].includes(
190
202
  target.getAttribute('role') || '',
191
203
  );
192
204
  });
@@ -206,10 +218,37 @@ function consumeShortcut(event: KeyboardEvent, definition: ShortcutDefinition):
206
218
  void definition.onTrigger(event);
207
219
  }
208
220
 
209
- export function ShortcutProvider({ children }: { children: ReactNode }) {
221
+ // Apps built before collections stopped binding Escape to an empty selection
222
+ // still register it, which swallows Escape for nothing. With collection items
223
+ // rendered and none selected, leave Escape to the enclosing surface (the Portal
224
+ // closes its frontmost layer, then returns to Inbox).
225
+ function isEmptyCollectionClear(shortcut: ShortcutDefinition, ownerDocument: Document, event: KeyboardEvent): boolean {
226
+ if (event.key !== 'Escape' || shortcut.id !== 'collection.clear') return false;
227
+ const items = collectionItems(ownerDocument).filter((item) => isInteractionElementVisible(item as HTMLElement));
228
+ return items.length > 0 && !items.some((item) => item.getAttribute('aria-selected') === 'true');
229
+ }
230
+
231
+ /** App views render in open shadow roots, which document queries do not enter. */
232
+ function collectionItems(root: Document | ShadowRoot): Element[] {
233
+ const items = Array.from(root.querySelectorAll('[data-notis-collection-item-id], [data-notis-row-id]'));
234
+ for (const element of Array.from(root.querySelectorAll('*'))) {
235
+ if (element.shadowRoot) items.push(...collectionItems(element.shadowRoot));
236
+ }
237
+ return items;
238
+ }
239
+
240
+ // Native dialogs make the background inert, but document capture listeners still
241
+ // receive their keyboard events. Leave those events to the dialog and its controls.
242
+ function isDialogEvent(event: KeyboardEvent): boolean {
243
+ return event.composedPath().some((target) =>
244
+ target instanceof HTMLElement && target.matches('dialog.notis-dialog[open]'),
245
+ );
246
+ }
247
+
248
+ export function ShortcutProvider({ children, isAvailable }: { children: ReactNode; isAvailable?: () => boolean }) {
210
249
  const parentRegistry = useContext(ShortcutContext);
211
250
  if (parentRegistry) return <>{children}</>;
212
- return <ShortcutProviderRoot>{children}</ShortcutProviderRoot>;
251
+ return <ShortcutProviderRoot isAvailable={isAvailable}>{children}</ShortcutProviderRoot>;
213
252
  }
214
253
 
215
254
  export interface ShortcutHelpEntry {
@@ -233,7 +272,7 @@ function applicableRegistrations(
233
272
  registrations: Iterable<ShortcutRegistration>,
234
273
  ): ShortcutRegistration[] {
235
274
  const ordered = Array.from(registrations)
236
- .filter((registration) => registration.enabled)
275
+ .filter((registration) => registration.enabled && registration.isAvailable?.() !== false)
237
276
  .sort((a, b) => b.priority - a.priority || b.order - a.order);
238
277
  const activeCollectionOwner = getShortcutCollectionState().activeOwnerId;
239
278
  const hasActiveCollection = Boolean(
@@ -248,6 +287,61 @@ function applicableRegistrations(
248
287
  : ordered;
249
288
  }
250
289
 
290
+ // Standalone app verification and legacy hosts may not install a provider.
291
+ // They must still arbitrate shortcuts together, not by effect/listener order.
292
+ const FALLBACK_REGISTRIES_SYMBOL = Symbol.for('notis.sdk.fallback_shortcut_registries');
293
+ interface FallbackShortcutRegistry {
294
+ registrations: Map<number, ShortcutRegistration>;
295
+ nextId: number;
296
+ dispatch: (event: KeyboardEvent) => void;
297
+ }
298
+ type FallbackShortcutGlobal = typeof globalThis & {
299
+ [FALLBACK_REGISTRIES_SYMBOL]?: WeakMap<Document, FallbackShortcutRegistry>;
300
+ };
301
+
302
+ function registerFallbackShortcut(input: Omit<ShortcutRegistration, 'id' | 'order'>): () => void {
303
+ const scope = globalThis as FallbackShortcutGlobal;
304
+ const registries = scope[FALLBACK_REGISTRIES_SYMBOL] ??= new WeakMap();
305
+ const ownerDocument = document;
306
+ let registry = registries.get(ownerDocument);
307
+ if (!registry) {
308
+ const registrations = new Map<number, ShortcutRegistration>();
309
+ registry = {
310
+ registrations,
311
+ nextId: 1,
312
+ dispatch: (event) => {
313
+ if (event.defaultPrevented || isDialogEvent(event)) return;
314
+ const editable = isEditableShortcutEvent(event);
315
+ const token = chordToken(eventChord(event));
316
+ for (const registration of applicableRegistrations(registrations.values())) {
317
+ for (const shortcut of registration.getShortcuts()) {
318
+ if (shortcut.enabled === false || (editable && !shortcut.allowInEditable) || (event.repeat && !shortcut.allowRepeat)
319
+ || isEmptyCollectionClear(shortcut, ownerDocument, event)) continue;
320
+ const match = parseShortcut(shortcut).some(
321
+ (candidate) => candidate.sequence.length === 1 && chordToken(candidate.sequence[0]!) === token,
322
+ );
323
+ if (match) {
324
+ consumeShortcut(event, shortcut);
325
+ return;
326
+ }
327
+ }
328
+ }
329
+ },
330
+ };
331
+ registries.set(ownerDocument, registry);
332
+ ownerDocument.addEventListener('keydown', registry.dispatch, true);
333
+ }
334
+ const id = registry.nextId++;
335
+ registry.registrations.set(id, { ...input, id, order: id });
336
+ return () => {
337
+ registry.registrations.delete(id);
338
+ if (registry.registrations.size === 0) {
339
+ ownerDocument.removeEventListener('keydown', registry.dispatch, true);
340
+ registries.delete(ownerDocument);
341
+ }
342
+ };
343
+ }
344
+
251
345
  function shortcutToken(raw: string): string | null {
252
346
  const definition: ShortcutDefinition = { id: '', keys: raw, onTrigger: () => undefined };
253
347
  const parsed = parseShortcut(definition)[0];
@@ -277,7 +371,7 @@ function collectShortcutHelpEntries(
277
371
  return entries;
278
372
  }
279
373
 
280
- function ShortcutProviderRoot({ children }: { children: ReactNode }) {
374
+ function ShortcutProviderRoot({ children, isAvailable }: { children: ReactNode; isAvailable?: () => boolean }) {
281
375
  const registrationsRef = useRef(new Map<number, ShortcutRegistration>());
282
376
  const nextIdRef = useRef(1);
283
377
  const nextOrderRef = useRef(1);
@@ -285,12 +379,21 @@ function ShortcutProviderRoot({ children }: { children: ReactNode }) {
285
379
  const [helpOpen, setHelpOpen] = useState(false);
286
380
  const helpOpenRef = useRef(false);
287
381
  const [, setRegistryVersion] = useState(0);
382
+ const availabilityRef = useRef(isAvailable);
288
383
 
289
384
  const updateHelpOpen = useCallback((open: boolean) => {
290
385
  helpOpenRef.current = open;
291
386
  setHelpOpen(open);
292
387
  }, []);
293
388
 
389
+ useLayoutEffect(() => {
390
+ availabilityRef.current = isAvailable;
391
+ if (isAvailable?.() === false) {
392
+ sequenceRef.current = { chords: [], at: 0 };
393
+ updateHelpOpen(false);
394
+ }
395
+ }, [isAvailable, updateHelpOpen]);
396
+
294
397
  const register = useCallback((input: Omit<ShortcutRegistration, 'id' | 'order'>) => {
295
398
  const id = nextIdRef.current++;
296
399
  registrationsRef.current.set(id, {
@@ -307,6 +410,11 @@ function ShortcutProviderRoot({ children }: { children: ReactNode }) {
307
410
 
308
411
  useEffect(() => {
309
412
  const handleKeyDown = (event: KeyboardEvent) => {
413
+ if (availabilityRef.current?.() === false) {
414
+ sequenceRef.current = { chords: [], at: 0 };
415
+ return;
416
+ }
417
+ if (event.defaultPrevented || isDialogEvent(event)) return;
310
418
  const editable = isEditableShortcutEvent(event);
311
419
  if (!editable && !event.repeat && event.key === '?' && !event.metaKey && !event.ctrlKey && !event.altKey) {
312
420
  event.preventDefault();
@@ -335,6 +443,7 @@ function ShortcutProviderRoot({ children }: { children: ReactNode }) {
335
443
  .filter((shortcut) => shortcut.enabled !== false)
336
444
  .filter((shortcut) => !editable || shortcut.allowInEditable)
337
445
  .filter((shortcut) => !event.repeat || shortcut.allowRepeat)
446
+ .filter((shortcut) => !isEmptyCollectionClear(shortcut, document, event))
338
447
  .flatMap(parseShortcut);
339
448
  if (candidates.length === 0) continue;
340
449
 
@@ -575,7 +684,10 @@ export function useShortcuts(
575
684
  priority = 0,
576
685
  sequenceTimeoutMs = 1500,
577
686
  collectionOwnerId,
687
+ isAvailable,
578
688
  } = options;
689
+ const availableRef = useRef(isAvailable);
690
+ availableRef.current = isAvailable;
579
691
 
580
692
  useEffect(() => {
581
693
  if (!registry || !enabled) return;
@@ -585,30 +697,24 @@ export function useShortcuts(
585
697
  priority: SHORTCUT_SCOPE_PRIORITY[scope] + priority,
586
698
  sequenceTimeoutMs,
587
699
  collectionOwnerId,
588
- getShortcuts: () => shortcutsRef.current,
700
+ isAvailable: () => availableRef.current?.() !== false,
701
+ getShortcuts: () => availableRef.current?.() === false ? [] : shortcutsRef.current,
589
702
  });
590
703
  }, [collectionOwnerId, enabled, priority, registry, scope, sequenceTimeoutMs]);
591
704
 
592
705
  // Compatibility fallback for SDK components rendered outside a provider.
593
706
  useEffect(() => {
594
707
  if (registry || !enabled) return;
595
- const handleKeyDown = (event: KeyboardEvent) => {
596
- if (isEditableShortcutEvent(event)) return;
597
- const token = chordToken(eventChord(event));
598
- for (const shortcut of shortcutsRef.current) {
599
- if (shortcut.enabled === false || event.repeat && !shortcut.allowRepeat) continue;
600
- const match = parseShortcut(shortcut).find(
601
- (candidate) => candidate.sequence.length === 1 && chordToken(candidate.sequence[0]!) === token,
602
- );
603
- if (match) {
604
- consumeShortcut(event, shortcut);
605
- return;
606
- }
607
- }
608
- };
609
- document.addEventListener('keydown', handleKeyDown, true);
610
- return () => document.removeEventListener('keydown', handleKeyDown, true);
611
- }, [enabled, registry]);
708
+ return registerFallbackShortcut({
709
+ enabled,
710
+ scope,
711
+ priority: SHORTCUT_SCOPE_PRIORITY[scope] + priority,
712
+ sequenceTimeoutMs,
713
+ collectionOwnerId,
714
+ isAvailable: () => availableRef.current?.() !== false,
715
+ getShortcuts: () => shortcutsRef.current,
716
+ });
717
+ }, [collectionOwnerId, enabled, priority, registry, scope, sequenceTimeoutMs]);
612
718
  }
613
719
 
614
720
  export function shortcutDisplay(raw: string): string {
@@ -0,0 +1,13 @@
1
+ /** Visibility across light and Shadow DOM, without relying on layout (virtual rows may have no rect). */
2
+ export function isInteractionElementVisible(element: HTMLElement | null): boolean {
3
+ if (!element?.isConnected) return false;
4
+ let current: Element | null = element;
5
+ while (current) {
6
+ if (current.hasAttribute('hidden') || current.hasAttribute('inert') || current.getAttribute('aria-hidden') === 'true') return false;
7
+ const style = current.ownerDocument.defaultView?.getComputedStyle(current);
8
+ if (style?.display === 'none' || style?.visibility === 'hidden') return false;
9
+ const root: Node = current.getRootNode();
10
+ current = current.parentElement ?? ('host' in root ? (root as ShadowRoot).host : null);
11
+ }
12
+ return true;
13
+ }
@@ -29,7 +29,8 @@ export type {
29
29
  SelectionMarqueeRect,
30
30
  UseCollectionInteractionsOptions,
31
31
  } from './hooks/useCollectionInteractions';
32
- export { MultiSelectActionBar } from './components/MultiSelectActionBar';
32
+ export { MultiSelectActionBar, BULK_ACTION_LAYOUT_EVENT } from './components/MultiSelectActionBar';
33
+ export type { BulkActionLayoutDetail } from './components/MultiSelectActionBar';
33
34
  export { MultiSelectCheckbox, MultiSelectCheckbox as SelectionCheckbox } from './components/MultiSelectCheckbox';
34
35
  export { MultiSelectDragOverlay, MultiSelectDragOverlay as SelectionMarquee } from './components/MultiSelectDragOverlay';
35
36
  export { ShortcutHints } from './components/ShortcutHints';
@@ -39,3 +40,6 @@ export type { MultiSelectDragOverlayProps } from './components/MultiSelectDragOv
39
40
  export type { MultiSelectCheckboxProps as SelectionCheckboxProps } from './components/MultiSelectCheckbox';
40
41
  export type { MultiSelectDragOverlayProps as SelectionMarqueeProps } from './components/MultiSelectDragOverlay';
41
42
  export type { ShortcutHint, ShortcutHintsProps } from './components/ShortcutHints';
43
+
44
+ export { useLongPressSelection } from './hooks/useLongPressSelection';
45
+ export { isInteractionElementVisible } from './interactions/visibility';
@@ -0,0 +1,39 @@
1
+ /** The native row contract, shared by direct tools and scoped Space actions. */
2
+ export type NativeField = { property_id: string } | { property: string } | {
3
+ column: 'record_key' | 'id' | 'title' | 'created_at' | 'updated_at' | 'last_edited_time' | 'content_type' | 'file_type';
4
+ };
5
+ export type NativeFilter = { and: NativeFilter[] } | { or: NativeFilter[] } | {
6
+ field: NativeField;
7
+ op: 'equals' | 'not_equals' | 'in' | 'contains' | 'not_contains' | 'does_not_contain'
8
+ | 'starts_with' | 'ends_with' | 'is_empty' | 'is_not_empty' | 'greater_than' | 'less_than'
9
+ | 'greater_than_or_equal_to' | 'less_than_or_equal_to' | 'before' | 'after' | 'on_or_before' | 'on_or_after';
10
+ value?: unknown;
11
+ };
12
+ export interface NativeQuery {
13
+ include_content?: boolean;
14
+ include_archived?: boolean;
15
+ mode?: 'rows' | 'search' | 'count' | 'aggregate';
16
+ filter?: NativeFilter;
17
+ sorts?: Array<{ field: NativeField; direction: 'asc' | 'desc' }>;
18
+ search?: { text: string; fields: NativeField[] };
19
+ aggregate?: { op: 'sum' | 'avg' | 'min' | 'max'; field: NativeField };
20
+ page_size?: number;
21
+ offset?: number;
22
+ }
23
+ export interface NativeInsert {
24
+ columns?: { title?: string | null; icon?: string | null; cover?: string | null };
25
+ properties?: Record<string, unknown>;
26
+ body?: { blocks: Array<Record<string, unknown>> };
27
+ }
28
+ export interface NativeUpdate extends NativeInsert { record_key: string; expected_revision: number; }
29
+ export interface NativeDelete { record_key: string; expected_revision: number; }
30
+ export interface NativeMutationReceipt {
31
+ record_key: string;
32
+ id: string;
33
+ database_id: string;
34
+ revision: number;
35
+ schema_revision: number;
36
+ receipt_id: string;
37
+ operation: 'insert' | 'update' | 'delete';
38
+ replayed?: boolean;
39
+ }
@@ -1,8 +1,9 @@
1
1
  'use client';
2
2
 
3
- import React, { createContext, useContext, type Context, type ReactNode } from 'react';
3
+ import React, { createContext, useContext, useMemo, type Context, type ReactNode } from 'react';
4
4
  import type { NotisRuntime } from './runtime';
5
5
  import { ShortcutProvider } from './interactions/shortcuts';
6
+ import { guardSpaceRuntime } from './space';
6
7
 
7
8
  const NOTIS_CONTEXT_SYMBOL = Symbol.for('notis.sdk.runtime_context');
8
9
 
@@ -24,13 +25,17 @@ const NotisContext = getNotisContext();
24
25
  * Provides the Notis runtime context to all child components. The portal owns
25
26
  * the runtime and injects it into the rendered app tree.
26
27
  */
27
- export function NotisProvider({ children, runtime }: { children: ReactNode; runtime: NotisRuntime | null }) {
28
+ export function NotisProvider({ children, runtime, onProtocolError, shortcutsAvailable }: {
29
+ children: ReactNode; runtime: NotisRuntime | null; onProtocolError?: (error: Error) => void;
30
+ shortcutsAvailable?: () => boolean;
31
+ }) {
32
+ const scopedRuntime = useMemo(() => guardSpaceRuntime(runtime, onProtocolError), [runtime, onProtocolError]);
28
33
  if (runtime === undefined) {
29
34
  throw new Error('NotisProvider requires an explicit runtime prop.');
30
35
  }
31
36
  return (
32
- <NotisContext.Provider value={runtime}>
33
- <ShortcutProvider>{children}</ShortcutProvider>
37
+ <NotisContext.Provider value={scopedRuntime}>
38
+ <ShortcutProvider isAvailable={shortcutsAvailable}>{children}</ShortcutProvider>
34
39
  </NotisContext.Provider>
35
40
  );
36
41
  }