@notis_ai/cli 0.2.18 → 0.2.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +42325 -18273
  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 +279 -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 -1570
  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 -3011
  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,83 @@
1
+ /** Small shared helpers of the toolkit pieces (not exported from `@notis/sdk/ui`). */
2
+
3
+ import { useCallback, useEffect, useLayoutEffect, useRef } from 'react';
4
+ import { labelValue, pick, type Words } from '../words/types';
5
+ import { useToolkitLocale } from './engine/hooks';
6
+
7
+ /** Joins class names, skipping empty ones. */
8
+ export function cx(...names: Array<string | false | null | undefined>): string {
9
+ return names.filter(Boolean).join(' ');
10
+ }
11
+
12
+ /** `(words) => string` in the toolkit locale. Plain strings pass through (Space code may pass its own words). */
13
+ export function useT(): (words: Words | string) => string {
14
+ const locale = useToolkitLocale();
15
+ return useCallback((words: Words | string) => pick(words, locale), [locale]);
16
+ }
17
+
18
+ /** `(label, value) => "Label: value"` in the toolkit locale (French: a narrow no-break space before the colon). */
19
+ export function useLabelValue(): (label: string, value: string) => string {
20
+ const locale = useToolkitLocale();
21
+ return useCallback((label: string, value: string) => labelValue(label, value, locale), [locale]);
22
+ }
23
+
24
+ /** A data attribute that is present (empty string) or absent, never "false". */
25
+ export function flag(on: boolean | undefined): '' | undefined {
26
+ return on ? '' : undefined;
27
+ }
28
+
29
+ export const useIsoLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;
30
+
31
+ /** The latest value, readable from callbacks registered once. */
32
+ export function useLatest<V>(value: V): { readonly current: V } {
33
+ const ref = useRef(value);
34
+ ref.current = value;
35
+ return ref;
36
+ }
37
+
38
+ let idCounter = 0;
39
+ /** A stable id for aria wiring (React 18 has useId, but the SDK supports any React the host ships). */
40
+ export function useStableId(prefix: string): string {
41
+ const ref = useRef<string | null>(null);
42
+ if (ref.current === null) {
43
+ idCounter += 1;
44
+ ref.current = `${prefix}-${idCounter}`;
45
+ }
46
+ return ref.current;
47
+ }
48
+
49
+ /** True when the event path (shadow roots included) passes through one of the elements. */
50
+ export function eventWithin(event: Event, elements: Array<Element | null | undefined>): boolean {
51
+ const path = typeof event.composedPath === 'function' ? event.composedPath() : [];
52
+ for (const element of elements) {
53
+ if (!element) continue;
54
+ if (path.includes(element)) return true;
55
+ if (event.target instanceof Node && element.contains(event.target)) return true;
56
+ }
57
+ return false;
58
+ }
59
+
60
+ /** The element that has focus, looking through open shadow roots. */
61
+ export function deepActiveElement(root: Document | ShadowRoot = document): Element | null {
62
+ let active = root.activeElement;
63
+ while (active && active.shadowRoot && active.shadowRoot.activeElement) active = active.shadowRoot.activeElement;
64
+ return active;
65
+ }
66
+
67
+ /**
68
+ * Scrolls the nearest sideways-scrolling ancestor so `el` is fully in view (a tab strip or toolbar on a phone).
69
+ * Unlike scrollIntoView it never scrolls the page up or down.
70
+ */
71
+ export function revealInline(el: HTMLElement | null | undefined): void {
72
+ if (!el || typeof window === 'undefined') return;
73
+ for (let box = el.parentElement; box; box = box.parentElement) {
74
+ if (box.scrollWidth <= box.clientWidth) continue;
75
+ const overflow = window.getComputedStyle(box).overflowX;
76
+ if (overflow !== 'auto' && overflow !== 'scroll') continue;
77
+ const boxRect = box.getBoundingClientRect();
78
+ const rect = el.getBoundingClientRect();
79
+ if (rect.left < boxRect.left) box.scrollLeft -= boxRect.left - rect.left;
80
+ else if (rect.right > boxRect.right) box.scrollLeft += rect.right - boxRect.right;
81
+ return;
82
+ }
83
+ }
@@ -0,0 +1,26 @@
1
+ /** Inclusive calendar ranges; Monday starts a week. Matches the server's shown reader. */
2
+ export const VIEW_PERIODS = ['today', 'tomorrow', 'this_week', 'last_week', 'this_month', 'last_month'] as const;
3
+ export function isViewDate(value: string): boolean {
4
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value) || value.startsWith('0000-')) return false;
5
+ const parsed = new Date(`${value}T00:00:00Z`);
6
+ return Number.isFinite(parsed.getTime()) && parsed.toISOString().slice(0, 10) === value;
7
+ }
8
+ export function isViewPeriod(value: string): boolean {
9
+ if ((VIEW_PERIODS as readonly string[]).includes(value)) return true;
10
+ const parts = value.split('/');
11
+ return parts.length === 2 && parts.every(isViewDate) && parts[0] <= parts[1];
12
+ }
13
+ export function resolveViewPeriod(value: string, timeZone = 'UTC', now = new Date()): { start: string; end: string } {
14
+ if (!isViewPeriod(value)) throw new Error('Invalid view period');
15
+ if (value.includes('/')) { const [start, end] = value.split('/'); return { start, end }; }
16
+ const parts = new Intl.DateTimeFormat('en-US', { timeZone, year: 'numeric', month: '2-digit', day: '2-digit' }).formatToParts(now);
17
+ const get = (type: string) => parts.find(part => part.type === type)!.value;
18
+ const start = new Date(`${get('year')}-${get('month')}-${get('day')}T00:00:00Z`);
19
+ if (value === 'tomorrow') start.setUTCDate(start.getUTCDate() + 1);
20
+ if (value.endsWith('week')) start.setUTCDate(start.getUTCDate() - (start.getUTCDay() + 6) % 7 - (value === 'last_week' ? 7 : 0));
21
+ if (value.endsWith('month')) { start.setUTCDate(1); if (value === 'last_month') start.setUTCMonth(start.getUTCMonth() - 1); }
22
+ const end = new Date(start);
23
+ if (value.endsWith('week')) end.setUTCDate(end.getUTCDate() + 6);
24
+ if (value.endsWith('month')) { end.setUTCMonth(end.getUTCMonth() + 1); end.setUTCDate(0); }
25
+ return { start: start.toISOString().slice(0, 10), end: end.toISOString().slice(0, 10) };
26
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Vite config builder for Notis Space views.
3
+ *
4
+ * Usage:
5
+ * ```ts
6
+ * // vite.config.ts
7
+ * import { notisViteConfig } from '@notis/sdk/vite';
8
+ * import workspace from './notis.config';
9
+ * export default notisViteConfig(workspace);
10
+ * ```
11
+ *
12
+ * Produces a library-mode ES module bundle with React externalized, and for
13
+ * Spaces the host-provided SDK too.
14
+ * Output: .notis/output/bundle/app.js + app.css
15
+ */
16
+
17
+ import * as vite from 'vite';
18
+ import type { SpacesWorkspace } from './space';
19
+ import { notisTailwindContent } from './tailwind';
20
+ import { NOTIS_SDK_HOST_MODULES, REACT_HOST_MODULES } from './runtimeModules';
21
+
22
+ const externalReact: string[] = [...REACT_HOST_MODULES];
23
+
24
+ // The oldest browsers that support CSS light-dark() natively. With Vite's
25
+ // default CSS target, Lightning CSS (Vite 8) rewrites light-dark(a, b) into
26
+ // var(--lightningcss-light, a) var(--lightningcss-dark, b), and those
27
+ // variables are only set by a stylesheet color-scheme rule. The Notis host
28
+ // sets color-scheme inline, so the lowered colours resolve to nothing and
29
+ // render transparent. Targeting these versions keeps light-dark() as written.
30
+ export const NOTIS_CSS_TARGET = ['chrome123', 'edge123', 'firefox120', 'safari17.5'];
31
+
32
+ /**
33
+ * Every selected Space builds with the same library config; the CLI writes the
34
+ * selected entry to `.notis/_entry.tsx`. The workspace index is accepted so
35
+ * vite.config.ts can pass its notis.config default export.
36
+ */
37
+ export function notisViteConfig(_workspace: SpacesWorkspace): vite.UserConfig {
38
+ // A Space bundle never contains the SDK: the host provides `@notis/sdk` and
39
+ // `@notis/sdk/ui` like React (runtimeModules.ts).
40
+ const external = [...externalReact, ...Object.keys(NOTIS_SDK_HOST_MODULES)];
41
+ // Vite 8 preserves external require() calls. App bundles run in a browser
42
+ // with an ESM import map, so bundled CommonJS dependencies must import React.
43
+ // Older pulled apps also receive this SDK; their Vite already does this.
44
+ const externalRequirePlugin = 'esmExternalRequirePlugin' in vite
45
+ && typeof vite.esmExternalRequirePlugin === 'function'
46
+ ? vite.esmExternalRequirePlugin({ external })
47
+ : null;
48
+ return {
49
+ plugins: [
50
+ notisTailwindContent(),
51
+ ...(externalRequirePlugin ? [externalRequirePlugin] : []),
52
+ // @vitejs/plugin-react is added by the consumer's vite.config.ts
53
+ // or auto-detected. We provide the config shape only.
54
+ ],
55
+ build: {
56
+ lib: {
57
+ entry: '.notis/_entry.tsx',
58
+ formats: ['es'],
59
+ fileName: () => 'app.js',
60
+ },
61
+ // Keep Vite's compatibility alias: SDK refresh also serves historical
62
+ // Vite 5/6/7 apps, which do not understand rolldownOptions.
63
+ rollupOptions: {
64
+ // On Vite 8 the plugin must own externalization as well as conversion:
65
+ // a top-level external match bypasses its require-to-import transform.
66
+ external: externalRequirePlugin ? [] : external,
67
+ output: {
68
+ globals: {
69
+ react: 'window.React',
70
+ 'react-dom': 'window.ReactDOM',
71
+ 'react-dom/client': 'window.ReactDOMClient',
72
+ 'react/jsx-runtime': 'window.React',
73
+ },
74
+ assetFileNames: 'app[extname]',
75
+ inlineDynamicImports: true,
76
+ },
77
+ },
78
+ outDir: '.notis/output/bundle',
79
+ emptyOutDir: true,
80
+ cssCodeSplit: false,
81
+ cssTarget: NOTIS_CSS_TARGET,
82
+ },
83
+ resolve: {
84
+ alias: {
85
+ '@': process.cwd(),
86
+ },
87
+ },
88
+ define: {
89
+ 'process.env.NODE_ENV': JSON.stringify('production'),
90
+ },
91
+ };
92
+ }
@@ -0,0 +1,295 @@
1
+ /**
2
+ * The standard action catalogue (toolkit spec 2.7). Notis owns these words and keys; a Space supplies only `run`.
3
+ * Pure: the React binding (`useActions`) lives in `ui/actions/`.
4
+ */
5
+
6
+ import { canonicalKey, keyList } from './keys';
7
+ import { fillWords, isWords, type KeySpec, type Words } from './types';
8
+
9
+ /** Phosphor icon name ('archive', 'trash', ...). The toolkit's Icon resolves it. */
10
+ export type IconName = string;
11
+
12
+ /**
13
+ * An icon name, or an icon element (`<Archive size={16} />`). The element is typed structurally so the words stay
14
+ * free of React; menus and the selection bar draw either.
15
+ */
16
+ export type ActionIcon = IconName | { readonly type: unknown; readonly props: unknown; readonly key?: unknown };
17
+
18
+ export type ActionTone = 'default' | 'danger';
19
+
20
+ export type UndoFn = () => void | Promise<void>;
21
+
22
+ /** One choice of a picker action (Move to, Assign). */
23
+ export interface ActionOption {
24
+ value: string;
25
+ label: Words | string;
26
+ icon?: ActionIcon;
27
+ /** A muted second line or right-hand hint. */
28
+ hint?: Words | string;
29
+ }
30
+
31
+ /**
32
+ * What a run may return besides nothing or an UndoFn.
33
+ * - `cancelled`: the user backed out (a confirm said no): no toast, the selection stays.
34
+ * - `undo`: the toast offers Undo (and binds Z).
35
+ * - `failed`: the items that did not change; they stay selected and the toast says how many.
36
+ * - `toast`: words for this run only (null: no toast).
37
+ * - `reason`: a muted second line under the toast's words: why items failed, or a note on what happened (it shows
38
+ * alone when the run has no toast words).
39
+ */
40
+ export interface ActionResult<T = unknown> {
41
+ cancelled?: boolean;
42
+ undo?: UndoFn;
43
+ failed?: readonly T[];
44
+ toast?: Words | string | null;
45
+ reason?: Words | string | null;
46
+ }
47
+
48
+ export type ActionRunResult<T = unknown> = void | undefined | UndoFn | ActionResult<T>;
49
+
50
+ /**
51
+ * Throw it from an action's `run`, or from any UndoFn a toast offers, to say why it failed: the failure toast ("That
52
+ * did not work. Try again.", "Could not undo that.") shows `reason` as its second line. Any other throw shows the
53
+ * failure words alone.
54
+ */
55
+ export class ActionError extends Error {
56
+ readonly reason: Words | string;
57
+ readonly code = 'notis_action_error';
58
+
59
+ constructor(reason: Words | string, options?: { cause?: unknown }) {
60
+ super(typeof reason === 'string' ? reason : reason.en);
61
+ this.name = 'ActionError';
62
+ this.reason = reason;
63
+ if (options && 'cause' in options) (this as { cause?: unknown }).cause = options.cause;
64
+ }
65
+ }
66
+
67
+ /** The reason an ActionError carries, else null. A Space bundles its own copy of the words, so this reads the shape. */
68
+ export function actionErrorReason(error: unknown): Words | string | null {
69
+ if (!error || typeof error !== 'object') return null;
70
+ const { name, code, reason } = error as { name?: unknown; code?: unknown; reason?: unknown };
71
+ if (name !== 'ActionError' || code !== 'notis_action_error') return null;
72
+ return typeof reason === 'string' || isWords(reason) ? reason : null;
73
+ }
74
+
75
+ export interface ActionDef<T = unknown> {
76
+ id: string;
77
+ /** Words, or one string for both languages. */
78
+ label: Words | string;
79
+ icon?: ActionIcon;
80
+ key?: KeySpec;
81
+ tone?: ActionTone;
82
+ /** Shown in the selection bar and run on every selected item. Default true. */
83
+ bulk?: boolean;
84
+ appliesTo?: (item: T) => boolean;
85
+ /**
86
+ * Return an UndoFn to make the toast offer Undo (and bind Z), or an ActionResult. A picker action receives the
87
+ * chosen option as `choice`.
88
+ */
89
+ run: (items: T[], choice?: ActionOption) => Promise<ActionRunResult<T>> | ActionRunResult<T>;
90
+ /**
91
+ * Words after a run; default from the catalogue. `null`, `false` or an explicit `toast: undefined` turns the
92
+ * toast off; the function may return null for no toast on that run.
93
+ */
94
+ toast?: ((n: number) => Words | string | null) | null | false;
95
+ /** The other half of a toggle (one key, one button): a mixed selection shows the first listed half. */
96
+ toggle?: string;
97
+ /** Picker choices (Move to, Assign): the list opens a menu of them and passes the pick to `run`. */
98
+ options?: readonly ActionOption[] | ((items: T[]) => readonly ActionOption[]);
99
+ /** The current value of the items (checked in the picker), when they all share one. */
100
+ value?: (items: T[]) => string | null | undefined;
101
+ }
102
+
103
+ export const STANDARD_ACTION_IDS = [
104
+ 'complete', 'archive', 'unarchive', 'star', 'unstar', 'later', 'move', 'assign', 'read', 'unread', 'delete',
105
+ 'progress', 'pause', 'resume', 'enable', 'disable', 'waiting',
106
+ ] as const;
107
+
108
+ export type StandardActionId = typeof STANDARD_ACTION_IDS[number];
109
+
110
+ export type ActionRef<T = unknown> = StandardActionId | (Partial<ActionDef<T>> & { id: string });
111
+
112
+ /** A toast with one and many forms; `{n}` in `other` is the count. */
113
+ export interface CountedWords {
114
+ one: Words;
115
+ other: Words;
116
+ }
117
+
118
+ export interface StandardAction {
119
+ id: StandardActionId;
120
+ label: Words;
121
+ icon: IconName;
122
+ key: KeySpec | null;
123
+ tone: ActionTone;
124
+ toast: CountedWords | null;
125
+ /** The other half of a toggle on the same key (star/unstar, read/unread). */
126
+ toggle?: StandardActionId;
127
+ /** Opens a picker instead of acting at once. */
128
+ picker?: boolean;
129
+ /** Not offered to Spaces yet (spec 6.10). */
130
+ managerOnly?: boolean;
131
+ }
132
+
133
+ const counted = (oneEn: string, otherEn: string, oneFr: string, otherFr: string): CountedWords => ({
134
+ one: { en: oneEn, fr: oneFr },
135
+ other: { en: otherEn, fr: otherFr },
136
+ });
137
+
138
+ const same = (en: string, fr: string): CountedWords => ({ one: { en, fr }, other: { en, fr } });
139
+
140
+ export const STANDARD_ACTIONS: Readonly<Record<StandardActionId, StandardAction>> = Object.freeze({
141
+ complete: {
142
+ id: 'complete', label: { en: 'Complete', fr: 'Terminer' }, icon: 'check', key: 'e', tone: 'default',
143
+ toast: counted('1 item completed.', '{n} items completed.', '1 élément terminé.', '{n} éléments terminés.'),
144
+ },
145
+ archive: {
146
+ id: 'archive', label: { en: 'Archive', fr: 'Archiver' }, icon: 'archive', key: 'e', tone: 'default',
147
+ toast: counted('1 item archived.', '{n} items archived.', '1 élément archivé.', '{n} éléments archivés.'),
148
+ },
149
+ unarchive: {
150
+ // Back out of the archive. A Space names its own first stop: `{ id: 'unarchive', label: { en: 'Move to Inbox', ... } }`.
151
+ id: 'unarchive', label: { en: 'Move to triage', fr: 'Déplacer vers le tri' }, icon: 'tray-arrow-up', key: 'shift+e',
152
+ tone: 'default',
153
+ toast: counted('1 item restored.', '{n} items restored.', '1 élément restauré.', '{n} éléments restaurés.'),
154
+ },
155
+ star: {
156
+ id: 'star', label: { en: 'Star', fr: 'Mettre en favori' }, icon: 'star', key: 's', tone: 'default', toggle: 'unstar',
157
+ toast: counted('1 item starred.', '{n} items starred.', '1 élément ajouté aux favoris.',
158
+ '{n} éléments ajoutés aux favoris.'),
159
+ },
160
+ unstar: {
161
+ id: 'unstar', label: { en: 'Unstar', fr: 'Retirer des favoris' }, icon: 'star', key: 's', tone: 'default',
162
+ toggle: 'star', toast: null,
163
+ },
164
+ later: {
165
+ id: 'later', label: { en: 'Later', fr: 'Plus tard' }, icon: 'clock', key: 'h', tone: 'default',
166
+ toast: same('Snoozed until tomorrow.', 'Reporté à demain.'),
167
+ },
168
+ move: {
169
+ id: 'move', label: { en: 'Move to…', fr: 'Déplacer vers…' }, icon: 'arrow-bend-up-right', key: 'm', tone: 'default',
170
+ toast: null, picker: true,
171
+ },
172
+ assign: {
173
+ id: 'assign', label: { en: 'Assign…', fr: 'Attribuer…' }, icon: 'user-plus', key: 'a', tone: 'default',
174
+ toast: null, picker: true,
175
+ },
176
+ read: {
177
+ id: 'read', label: { en: 'Mark as read', fr: 'Marquer comme lu' }, icon: 'envelope-open', key: 'u', tone: 'default',
178
+ toggle: 'unread', toast: null,
179
+ },
180
+ unread: {
181
+ id: 'unread', label: { en: 'Mark as unread', fr: 'Marquer comme non lu' }, icon: 'envelope-simple', key: 'u',
182
+ tone: 'default', toggle: 'read', toast: null,
183
+ },
184
+ delete: {
185
+ id: 'delete', label: { en: 'Delete', fr: 'Supprimer' }, icon: 'trash', key: '#', tone: 'danger',
186
+ toast: counted('1 item deleted.', '{n} items deleted.', '1 élément supprimé.', '{n} éléments supprimés.'),
187
+ },
188
+ progress: {
189
+ id: 'progress', label: { en: 'Start progress', fr: 'Passer en cours' }, icon: 'circle-half', key: null,
190
+ tone: 'default', toast: null,
191
+ },
192
+ pause: {
193
+ id: 'pause', label: { en: 'Pause', fr: 'Mettre en pause' }, icon: 'pause', key: null, tone: 'default',
194
+ toggle: 'resume', toast: null,
195
+ },
196
+ resume: {
197
+ id: 'resume', label: { en: 'Resume', fr: 'Reprendre' }, icon: 'play', key: null, tone: 'default',
198
+ toggle: 'pause', toast: null,
199
+ },
200
+ enable: {
201
+ id: 'enable', label: { en: 'Enable', fr: 'Activer' }, icon: 'toggle-right', key: null, tone: 'default',
202
+ toggle: 'disable', toast: null,
203
+ },
204
+ disable: {
205
+ id: 'disable', label: { en: 'Disable', fr: 'Désactiver' }, icon: 'toggle-left', key: null, tone: 'default',
206
+ toggle: 'enable', toast: null,
207
+ },
208
+ waiting: {
209
+ id: 'waiting', label: { en: 'Waiting', fr: 'En attente' }, icon: 'hourglass', key: 'w', tone: 'default', toast: null,
210
+ managerOnly: true,
211
+ },
212
+ });
213
+
214
+ export function isStandardActionId(id: unknown): id is StandardActionId {
215
+ return typeof id === 'string' && Object.prototype.hasOwnProperty.call(STANDARD_ACTIONS, id);
216
+ }
217
+
218
+ /** The toast after `n` items, count filled in; null when the action has none. */
219
+ export function actionToast(id: StandardActionId, n: number): Words | null {
220
+ const toast = STANDARD_ACTIONS[id].toast;
221
+ if (!toast) return null;
222
+ return fillWords(n === 1 ? toast.one : toast.other, { n });
223
+ }
224
+
225
+ /**
226
+ * The catalogue defaults for a ref, with the Space's overrides on top. `run` comes from the Space. An own `toast`
227
+ * key set to undefined, null or false turns the default toast off (the result then carries `toast: null`).
228
+ */
229
+ export function resolveActionRef<T>(ref: ActionRef<T>): Partial<ActionDef<T>> & { id: string } {
230
+ if (typeof ref === 'string') return standardDefaults<T>(ref);
231
+ const toastOff = Object.prototype.hasOwnProperty.call(ref, 'toast') && !ref.toast;
232
+ const merged: Partial<ActionDef<T>> & { id: string } = isStandardActionId(ref.id)
233
+ ? { ...standardDefaults<T>(ref.id), ...stripUndefined(ref) }
234
+ : { ...ref };
235
+ if (toastOff) merged.toast = null;
236
+ return merged;
237
+ }
238
+
239
+ /** One string for both languages, or the words as given. */
240
+ export function toWords(value: Words | string): Words {
241
+ return typeof value === 'string' ? { en: value, fr: value } : value;
242
+ }
243
+
244
+ function standardDefaults<T>(id: StandardActionId): Partial<ActionDef<T>> & { id: string } {
245
+ const action = STANDARD_ACTIONS[id];
246
+ return {
247
+ id,
248
+ label: action.label,
249
+ icon: action.icon,
250
+ key: action.key ?? undefined,
251
+ tone: action.tone,
252
+ toast: action.toast ? (n: number) => actionToast(id, n)! : undefined,
253
+ ...(action.toggle ? { toggle: action.toggle } : {}),
254
+ };
255
+ }
256
+
257
+ function stripUndefined<V extends object>(value: V): Partial<V> {
258
+ const out: Partial<V> = {};
259
+ for (const [k, v] of Object.entries(value)) if (v !== undefined) (out as Record<string, unknown>)[k] = v;
260
+ return out;
261
+ }
262
+
263
+ export interface ActionKeyClash {
264
+ key: string;
265
+ ids: string[];
266
+ }
267
+
268
+ /**
269
+ * Actions of one list that share a key. Toggle pairs (star/unstar, read/unread) are one key by design and do not
270
+ * clash. complete and archive both take E, so a list may list only one.
271
+ */
272
+ export function actionKeyClashes(refs: readonly ActionRef<unknown>[]): ActionKeyClash[] {
273
+ const byKey = new Map<string, string[]>();
274
+ const toggles = new Map<string, string>();
275
+ for (const ref of refs) {
276
+ const resolved = resolveActionRef(ref);
277
+ if (resolved.toggle) toggles.set(resolved.id, resolved.toggle);
278
+ for (const spec of keyList(resolved.key)) {
279
+ const canonical = canonicalKey(spec);
280
+ const ids = byKey.get(canonical) ?? [];
281
+ if (!ids.includes(resolved.id)) ids.push(resolved.id);
282
+ byKey.set(canonical, ids);
283
+ }
284
+ }
285
+ const clashes: ActionKeyClash[] = [];
286
+ for (const [key, ids] of byKey) {
287
+ const left = ids.filter((id, index) => {
288
+ const partner = toggles.get(id);
289
+ // Keep one member of each toggle pair.
290
+ return !(partner && ids.indexOf(partner) !== -1 && ids.indexOf(partner) < index);
291
+ });
292
+ if (left.length > 1) clashes.push({ key, ids: left });
293
+ }
294
+ return clashes;
295
+ }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Key clash rules (toolkit spec 2.1). The engine runs them at registration; the design check (stream C) runs
3
+ * `checkClashes` on a Space's declared shortcuts at build time. Pure.
4
+ */
5
+
6
+ import { canonicalKey, isHostReserved, keyList, parseKeySpec } from './keys';
7
+ import { SCOPE_ORDER, type KeySpec, type Scope } from './types';
8
+
9
+ export type ClashKind =
10
+ /** A Space binds a key the Notis host owns. Error. */
11
+ | 'reserved'
12
+ /** A Space registers at app scope, which is host only (spec 6.7). Error. */
13
+ | 'app-scope'
14
+ /** Same key, same scope, same owner, both available. Error. */
15
+ | 'duplicate'
16
+ /** Same key at different scopes: legal, the higher scope wins. Info. */
17
+ | 'shadowed'
18
+ /** A single key that also starts a sequence in the same scope and owner ('g' and 'g i'): the single key wins. Info. */
19
+ | 'prefix'
20
+ /** The spec does not parse. Error. */
21
+ | 'invalid';
22
+
23
+ export interface ClashCandidate {
24
+ id: string;
25
+ keys: KeySpec | readonly KeySpec[];
26
+ scope: Scope;
27
+ /** Who registered it: a Space id, a retained view, or 'notis'. Default 'notis'. */
28
+ owner?: string;
29
+ /** 'space' defs may not use reserved keys or the app scope. Default 'space' when owner is set and not 'notis'. */
30
+ ownerKind?: 'space' | 'host';
31
+ /** Default true. Unavailable defs (hidden retained views) never clash. */
32
+ available?: boolean;
33
+ }
34
+
35
+ export interface Clash {
36
+ kind: ClashKind;
37
+ severity: 'error' | 'info';
38
+ /** Canonical key ('shift+e', 'g i'). */
39
+ key: string;
40
+ ids: string[];
41
+ scope?: Scope;
42
+ scopes?: Scope[];
43
+ owner?: string;
44
+ message: string;
45
+ }
46
+
47
+ const PLATFORMS = ['mac', 'other'] as const;
48
+
49
+ /** Canonical forms of a def's keys on both platforms ('ctrl+a' equals 'mod+a' off a Mac). */
50
+ export function keyFingerprints(keys: KeySpec | readonly KeySpec[]): Map<string, string> {
51
+ const out = new Map<string, string>();
52
+ for (const spec of keyList(keys)) {
53
+ for (const platform of PLATFORMS) out.set(`${platform}:${canonicalKey(spec, platform)}`, canonicalKey(spec, 'mac'));
54
+ }
55
+ return out;
56
+ }
57
+
58
+ export function ownerOf(candidate: ClashCandidate): string {
59
+ return candidate.owner ?? 'notis';
60
+ }
61
+
62
+ export function ownerKindOf(candidate: ClashCandidate): 'space' | 'host' {
63
+ if (candidate.ownerKind) return candidate.ownerKind;
64
+ return ownerOf(candidate) === 'notis' ? 'host' : 'space';
65
+ }
66
+
67
+ /** Errors a single def has on its own (invalid, reserved, app scope). */
68
+ export function selfClashes(candidate: ClashCandidate): Clash[] {
69
+ const clashes: Clash[] = [];
70
+ const space = ownerKindOf(candidate) === 'space';
71
+ for (const spec of keyList(candidate.keys)) {
72
+ try { parseKeySpec(spec); } catch (error) {
73
+ clashes.push({
74
+ kind: 'invalid', severity: 'error', key: spec, ids: [candidate.id], scope: candidate.scope,
75
+ owner: ownerOf(candidate), message: (error as Error).message,
76
+ });
77
+ continue;
78
+ }
79
+ if (space && isHostReserved(spec)) {
80
+ clashes.push({
81
+ kind: 'reserved', severity: 'error', key: canonicalKey(spec), ids: [candidate.id], scope: candidate.scope,
82
+ owner: ownerOf(candidate),
83
+ message: `"${candidate.id}" uses ${canonicalKey(spec)}, which Notis keeps for itself. Pick another key.`,
84
+ });
85
+ }
86
+ }
87
+ if (space && candidate.scope === 'app') {
88
+ clashes.push({
89
+ kind: 'app-scope', severity: 'error', key: keyList(candidate.keys).join(', '), ids: [candidate.id],
90
+ scope: 'app', owner: ownerOf(candidate),
91
+ message: `"${candidate.id}" registers at app scope, which only Notis uses. Use route or a lower scope.`,
92
+ });
93
+ }
94
+ return clashes;
95
+ }
96
+
97
+ function safeFingerprints(keys: KeySpec | readonly KeySpec[]): Map<string, string> {
98
+ try { return keyFingerprints(keys); } catch { return new Map(); }
99
+ }
100
+
101
+ /** Errors and infos between two defs, `later` registered after `earlier`. */
102
+ export function pairClashes(earlier: ClashCandidate, later: ClashCandidate): Clash[] {
103
+ if (earlier.available === false || later.available === false) return [];
104
+ const a = safeFingerprints(earlier.keys);
105
+ const b = safeFingerprints(later.keys);
106
+ const shared = new Set<string>();
107
+ for (const [fingerprint, key] of b) if (a.has(fingerprint)) shared.add(key);
108
+ const clashes: Clash[] = [];
109
+ const sameOwner = ownerOf(earlier) === ownerOf(later);
110
+ for (const key of shared) {
111
+ if (earlier.scope === later.scope && sameOwner) {
112
+ clashes.push({
113
+ kind: 'duplicate', severity: 'error', key, ids: [earlier.id, later.id], scope: later.scope,
114
+ owner: ownerOf(later),
115
+ message: `NotisKeyClash: "${earlier.id}" and "${later.id}" both use ${key} in the ${later.scope} scope.`,
116
+ });
117
+ } else if (earlier.scope !== later.scope) {
118
+ const [low, high] = SCOPE_ORDER[earlier.scope] < SCOPE_ORDER[later.scope] ? [earlier, later] : [later, earlier];
119
+ clashes.push({
120
+ kind: 'shadowed', severity: 'info', key, ids: [high.id, low.id], scopes: [high.scope, low.scope],
121
+ message: `${key}: "${high.id}" (${high.scope}) is asked before "${low.id}" (${low.scope}).`,
122
+ });
123
+ }
124
+ }
125
+ if (earlier.scope === later.scope && sameOwner) {
126
+ for (const [single, sequence] of [[earlier, later], [later, earlier]] as const) {
127
+ for (const one of keyList(single.keys)) {
128
+ for (const seq of keyList(sequence.keys)) {
129
+ let s: ReturnType<typeof parseKeySpec>; let q: ReturnType<typeof parseKeySpec>;
130
+ try { s = parseKeySpec(one); q = parseKeySpec(seq); } catch { continue; }
131
+ if (s.length !== 1 || q.length < 2) continue;
132
+ if (canonicalKey(one) !== canonicalKey(seq).split(' ')[0]) continue;
133
+ clashes.push({
134
+ kind: 'prefix', severity: 'info', key: canonicalKey(one), ids: [single.id, sequence.id],
135
+ scope: single.scope, owner: ownerOf(single),
136
+ message: `${canonicalKey(one)} runs "${single.id}" at once, so "${sequence.id}" (${canonicalKey(seq)}) never starts.`,
137
+ });
138
+ }
139
+ }
140
+ }
141
+ }
142
+ return clashes;
143
+ }
144
+
145
+ /** Every clash in a list of defs, in registration order. */
146
+ export function checkClashes(defs: readonly ClashCandidate[]): Clash[] {
147
+ const clashes: Clash[] = [];
148
+ defs.forEach((def, index) => {
149
+ clashes.push(...selfClashes(def));
150
+ for (let i = 0; i < index; i += 1) clashes.push(...pairClashes(defs[i]!, def));
151
+ });
152
+ return clashes;
153
+ }
154
+
155
+ /** Only the clashes that must stop a build or throw in dev. */
156
+ export function clashErrors(defs: readonly ClashCandidate[]): Clash[] {
157
+ return checkClashes(defs).filter((clash) => clash.severity === 'error');
158
+ }