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