@notis_ai/cli 0.2.17 → 0.2.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (395) hide show
  1. package/README.md +542 -146
  2. package/bin/check-runtime.js +15 -0
  3. package/bin/notis.js +2 -0
  4. package/config/notis_app_design_rules.json +320 -0
  5. package/config/portal-route-roots.json +45 -0
  6. package/dist/agent-hooks/notis-agent-hook.mjs +42323 -18258
  7. package/dist/base-skills/notis-cli/.notis-base-skill.json +1 -0
  8. package/dist/base-skills/notis-cli/SKILL.md +51 -280
  9. package/dist/base-skills/notis-cli/references/app-delivery.md +17 -0
  10. package/dist/base-skills/notis-cli/references/intelligence.md +94 -0
  11. package/dist/base-skills/notis-cli/references/native-databases.md +21 -0
  12. package/dist/base-skills/notis-cli/references/tool-examples.md +56 -0
  13. package/dist/base-skills/notis-cli/references/troubleshooting.md +67 -0
  14. package/dist/base-skills/notis-query/.notis-base-skill.json +1 -0
  15. package/dist/base-skills/notis-query/SKILL.md +35 -699
  16. package/dist/base-skills/notis-query/references/database-discovery.md +59 -0
  17. package/dist/base-skills/notis-query/references/documents.md +74 -0
  18. package/dist/base-skills/notis-query/references/query.md +76 -0
  19. package/dist/base-skills/notis-spaces/.notis-base-skill.json +1 -0
  20. package/dist/base-skills/notis-spaces/SKILL.md +218 -0
  21. package/dist/base-skills/notis-spaces/references/architecture.md +116 -0
  22. package/dist/base-skills/notis-spaces/references/context.md +81 -0
  23. package/dist/base-skills/notis-spaces/references/design.md +141 -0
  24. package/dist/base-skills/notis-spaces/references/fixtures.md +29 -0
  25. package/dist/base-skills/notis-spaces/references/memory.md +58 -0
  26. package/dist/base-skills/notis-spaces/references/reading.md +73 -0
  27. package/dist/base-skills/notis-spaces/references/release.md +120 -0
  28. package/dist/base-skills/notis-spaces/references/reports.md +77 -0
  29. package/dist/base-skills/notis-spaces/references/sdk.md +273 -0
  30. package/dist/base-skills/notis-spaces/references/store.md +118 -0
  31. package/dist/base-skills/notis-spaces/references/troubleshooting.md +28 -0
  32. package/dist/base-skills/notis-spaces/references/views.md +229 -0
  33. package/{template/packages → dist}/sdk/package.json +9 -10
  34. package/dist/sdk/src/agentContext.ts +36 -0
  35. package/dist/sdk/src/bundleModule.ts +175 -0
  36. package/dist/sdk/src/components/Dialog.tsx +103 -0
  37. package/dist/sdk/src/components/DocumentEditor.tsx +113 -0
  38. package/dist/sdk/src/components/DocumentPage.tsx +184 -0
  39. package/{template/packages → dist}/sdk/src/components/Markdown.tsx +9 -3
  40. package/{template/packages → dist}/sdk/src/components/MarkdownEditor.tsx +26 -5
  41. package/{template/packages → dist}/sdk/src/components/MultiSelectActionBar.tsx +95 -12
  42. package/{template/packages → dist}/sdk/src/components/MultiSelectCheckbox.tsx +4 -2
  43. package/dist/sdk/src/components/NotisCommentBoundary.tsx +172 -0
  44. package/{template/packages → dist}/sdk/src/components/NotisSelectionBoundary.tsx +9 -5
  45. package/dist/sdk/src/components/RecordComponents.tsx +111 -0
  46. package/dist/sdk/src/components/Skeleton.tsx +38 -0
  47. package/dist/sdk/src/config.ts +31 -0
  48. package/{template/packages → dist}/sdk/src/documents.ts +21 -6
  49. package/dist/sdk/src/hooks/useAction.ts +28 -0
  50. package/dist/sdk/src/hooks/useAgentContext.ts +23 -0
  51. package/{template/packages → dist}/sdk/src/hooks/useBackend.ts +3 -2
  52. package/dist/sdk/src/hooks/useCloudComputer.ts +94 -0
  53. package/dist/sdk/src/hooks/useCloudComputerShell.ts +114 -0
  54. package/{template/packages → dist}/sdk/src/hooks/useCollectionInteractions.ts +157 -147
  55. package/dist/sdk/src/hooks/useDatabaseSchema.ts +50 -0
  56. package/{template/packages → dist}/sdk/src/hooks/useDatabaseSubscription.ts +2 -2
  57. package/dist/sdk/src/hooks/useDocument.ts +51 -0
  58. package/{template/packages → dist}/sdk/src/hooks/useDocuments.ts +25 -59
  59. package/{template/packages → dist}/sdk/src/hooks/useHandover.ts +7 -7
  60. package/dist/sdk/src/hooks/useLongPressSelection.ts +79 -0
  61. package/dist/sdk/src/hooks/useMarqueeSelection.ts +194 -0
  62. package/{template/packages → dist}/sdk/src/hooks/useMultiSelect.ts +2 -8
  63. package/dist/sdk/src/hooks/useNativeDocumentBody.ts +50 -0
  64. package/dist/sdk/src/hooks/useNativeDocuments.ts +143 -0
  65. package/{template/packages → dist}/sdk/src/hooks/useNotis.ts +6 -1
  66. package/dist/sdk/src/hooks/useNotisNavigation.ts +70 -0
  67. package/dist/sdk/src/hooks/useQuery.ts +81 -0
  68. package/dist/sdk/src/hooks/useToolQuery.ts +12 -0
  69. package/dist/sdk/src/hooks/useTopBarSearch.ts +102 -0
  70. package/dist/sdk/src/hooks/useTopBarTitleLeaf.ts +75 -0
  71. package/{template/packages → dist}/sdk/src/hooks/useUpsertDocument.ts +1 -1
  72. package/dist/sdk/src/hooks/useViewParams.ts +195 -0
  73. package/dist/sdk/src/hooks/useViewerReads.ts +129 -0
  74. package/{template/packages → dist}/sdk/src/index.ts +64 -1
  75. package/{template/packages → dist}/sdk/src/interactions/actions.ts +14 -1
  76. package/{template/packages → dist}/sdk/src/interactions/shortcuts.tsx +129 -23
  77. package/dist/sdk/src/interactions/visibility.ts +13 -0
  78. package/{template/packages → dist}/sdk/src/interactions.ts +5 -1
  79. package/dist/sdk/src/nativeData.ts +39 -0
  80. package/{template/packages → dist}/sdk/src/provider.tsx +9 -4
  81. package/dist/sdk/src/queryCache.ts +292 -0
  82. package/dist/sdk/src/queryErrors.ts +51 -0
  83. package/dist/sdk/src/runtime.ts +675 -0
  84. package/dist/sdk/src/runtimeModules.ts +33 -0
  85. package/dist/sdk/src/space.ts +442 -0
  86. package/dist/sdk/src/standardViewParams.ts +36 -0
  87. package/{template/packages → dist}/sdk/src/styles.css +81 -16
  88. package/dist/sdk/src/tailwind.ts +66 -0
  89. package/dist/sdk/src/ui/charts/RenderChartData.tsx +25 -0
  90. package/dist/sdk/src/ui/engine/core.ts +392 -0
  91. package/dist/sdk/src/ui/engine/dom.ts +183 -0
  92. package/dist/sdk/src/ui/engine/hooks.ts +195 -0
  93. package/dist/sdk/src/ui/engine/instance.ts +63 -0
  94. package/dist/sdk/src/ui/engine/keycapContext.ts +2 -0
  95. package/dist/sdk/src/ui/engine/registry.ts +262 -0
  96. package/dist/sdk/src/ui/engine/types.ts +62 -0
  97. package/dist/sdk/src/ui/icons.tsx +91 -0
  98. package/dist/sdk/src/ui/util.ts +83 -0
  99. package/dist/sdk/src/viewPeriod.ts +26 -0
  100. package/dist/sdk/src/vite.ts +92 -0
  101. package/dist/sdk/src/words/actions.ts +295 -0
  102. package/dist/sdk/src/words/clashes.ts +158 -0
  103. package/dist/sdk/src/words/format.ts +136 -0
  104. package/dist/sdk/src/words/index.ts +13 -0
  105. package/dist/sdk/src/words/keymap.ts +72 -0
  106. package/dist/sdk/src/words/keys.ts +297 -0
  107. package/dist/sdk/src/words/shell.ts +46 -0
  108. package/dist/sdk/src/words/types.ts +65 -0
  109. package/dist/skill-sync/index.js +832 -151
  110. package/dist/skill-sync/index.js.map +4 -4
  111. package/dist/skill-sync-worker.mjs +3941 -0
  112. package/dist/space-harness/host.js +1460 -0
  113. package/dist/space-sdk/dist/chunk-ZSN57ATH.js +5 -0
  114. package/dist/space-sdk/dist/index.js +15 -0
  115. package/dist/space-sdk/dist/ui.js +18 -0
  116. package/dist/space-sdk/package.json +33 -0
  117. package/dist/space-sdk/src/agentContext.d.ts +36 -0
  118. package/dist/space-sdk/src/components/Dialog.d.ts +20 -0
  119. package/dist/space-sdk/src/components/DocumentEditor.d.ts +46 -0
  120. package/dist/space-sdk/src/components/DocumentPage.d.ts +53 -0
  121. package/dist/space-sdk/src/components/Markdown.d.ts +18 -0
  122. package/dist/space-sdk/src/components/MarkdownEditor.d.ts +11 -0
  123. package/dist/space-sdk/src/components/MultiSelectActionBar.d.ts +31 -0
  124. package/dist/space-sdk/src/components/MultiSelectCheckbox.d.ts +14 -0
  125. package/dist/space-sdk/src/components/MultiSelectDragOverlay.d.ts +8 -0
  126. package/dist/space-sdk/src/components/NotisCommentBoundary.d.ts +36 -0
  127. package/dist/space-sdk/src/components/NotisSelectionBoundary.d.ts +16 -0
  128. package/dist/space-sdk/src/components/RecordComponents.d.ts +64 -0
  129. package/dist/space-sdk/src/components/ShortcutHints.d.ts +12 -0
  130. package/dist/space-sdk/src/components/Skeleton.d.ts +23 -0
  131. package/dist/space-sdk/src/config.ts +31 -0
  132. package/dist/space-sdk/src/documents.d.ts +59 -0
  133. package/dist/space-sdk/src/hooks/useAction.d.ts +8 -0
  134. package/dist/space-sdk/src/hooks/useActiveResource.d.ts +3 -0
  135. package/dist/space-sdk/src/hooks/useAgentContext.d.ts +7 -0
  136. package/dist/space-sdk/src/hooks/useBackend.d.ts +23 -0
  137. package/dist/space-sdk/src/hooks/useCloudComputer.d.ts +45 -0
  138. package/dist/space-sdk/src/hooks/useCloudComputerShell.d.ts +49 -0
  139. package/dist/space-sdk/src/hooks/useCollectionInteractions.d.ts +118 -0
  140. package/dist/space-sdk/src/hooks/useDatabaseSchema.d.ts +14 -0
  141. package/dist/space-sdk/src/hooks/useDatabaseSubscription.d.ts +26 -0
  142. package/dist/space-sdk/src/hooks/useDocument.d.ts +22 -0
  143. package/dist/space-sdk/src/hooks/useDocuments.d.ts +31 -0
  144. package/dist/space-sdk/src/hooks/useHandover.d.ts +43 -0
  145. package/dist/space-sdk/src/hooks/useLongPressSelection.d.ts +9 -0
  146. package/dist/space-sdk/src/hooks/useMarqueeSelection.d.ts +57 -0
  147. package/dist/space-sdk/src/hooks/useMultiSelect.d.ts +51 -0
  148. package/dist/space-sdk/src/hooks/useNativeDocumentBody.d.ts +24 -0
  149. package/dist/space-sdk/src/hooks/useNativeDocuments.d.ts +55 -0
  150. package/dist/space-sdk/src/hooks/useNotis.d.ts +25 -0
  151. package/dist/space-sdk/src/hooks/useNotisNavigation.d.ts +33 -0
  152. package/dist/space-sdk/src/hooks/useQuery.d.ts +22 -0
  153. package/dist/space-sdk/src/hooks/useTool.d.ts +20 -0
  154. package/dist/space-sdk/src/hooks/useToolQuery.d.ts +3 -0
  155. package/dist/space-sdk/src/hooks/useTools.d.ts +15 -0
  156. package/dist/space-sdk/src/hooks/useTopBarSearch.d.ts +48 -0
  157. package/dist/space-sdk/src/hooks/useTopBarTitleLeaf.d.ts +24 -0
  158. package/dist/space-sdk/src/hooks/useUpsertDocument.d.ts +27 -0
  159. package/dist/space-sdk/src/hooks/useViewParams.d.ts +76 -0
  160. package/dist/space-sdk/src/hooks/useViewerReads.d.ts +38 -0
  161. package/dist/space-sdk/src/index.d.ts +98 -0
  162. package/dist/space-sdk/src/interactions/actions.d.ts +33 -0
  163. package/dist/space-sdk/src/interactions/shortcuts.d.ts +42 -0
  164. package/dist/space-sdk/src/interactions/visibility.d.ts +2 -0
  165. package/dist/space-sdk/src/nativeData.d.ts +66 -0
  166. package/dist/space-sdk/src/provider.d.ts +17 -0
  167. package/dist/space-sdk/src/queryCache.d.ts +45 -0
  168. package/dist/space-sdk/src/queryErrors.ts +51 -0
  169. package/dist/space-sdk/src/runtime.d.ts +615 -0
  170. package/dist/space-sdk/src/runtimeModules.ts +33 -0
  171. package/dist/space-sdk/src/space.ts +442 -0
  172. package/dist/space-sdk/src/standardViewParams.d.ts +15 -0
  173. package/dist/space-sdk/src/tailwind-candidates.d.ts +3418 -0
  174. package/dist/space-sdk/src/tailwind.ts +66 -0
  175. package/dist/space-sdk/src/ui/actions/ActionToast.d.ts +15 -0
  176. package/dist/space-sdk/src/ui/actions/actionWords.d.ts +16 -0
  177. package/dist/space-sdk/src/ui/actions/index.d.ts +7 -0
  178. package/dist/space-sdk/src/ui/actions/useActions.d.ts +69 -0
  179. package/dist/space-sdk/src/ui/actions/useToast.d.ts +41 -0
  180. package/dist/space-sdk/src/ui/blocks/Tabs.d.ts +139 -0
  181. package/dist/space-sdk/src/ui/blocks/blocks.d.ts +217 -0
  182. package/dist/space-sdk/src/ui/blocks/index.d.ts +4 -0
  183. package/dist/space-sdk/src/ui/board/BoardView.d.ts +36 -0
  184. package/dist/space-sdk/src/ui/board/boardModel.d.ts +46 -0
  185. package/dist/space-sdk/src/ui/board/boardWords.d.ts +9 -0
  186. package/dist/space-sdk/src/ui/board/index.d.ts +5 -0
  187. package/dist/space-sdk/src/ui/calendar/CalendarView.d.ts +49 -0
  188. package/dist/space-sdk/src/ui/calendar/DayPicker.d.ts +23 -0
  189. package/dist/space-sdk/src/ui/calendar/calendarMath.d.ts +81 -0
  190. package/dist/space-sdk/src/ui/charts/Chart.d.ts +203 -0
  191. package/dist/space-sdk/src/ui/charts/ChartTable.d.ts +37 -0
  192. package/dist/space-sdk/src/ui/charts/RenderChartData.d.ts +16 -0
  193. package/dist/space-sdk/src/ui/charts/StatRow.d.ts +59 -0
  194. package/dist/space-sdk/src/ui/charts/chartData.d.ts +84 -0
  195. package/dist/space-sdk/src/ui/comments/CommentPane.d.ts +58 -0
  196. package/dist/space-sdk/src/ui/comments/commentModel.d.ts +157 -0
  197. package/dist/space-sdk/src/ui/comments/commentWords.d.ts +60 -0
  198. package/dist/space-sdk/src/ui/comments/index.d.ts +4 -0
  199. package/dist/space-sdk/src/ui/comments/useComments.d.ts +146 -0
  200. package/dist/space-sdk/src/ui/engine/HelpSheet.d.ts +12 -0
  201. package/dist/space-sdk/src/ui/engine/KeyboardEngine.d.ts +27 -0
  202. package/dist/space-sdk/src/ui/engine/core.d.ts +73 -0
  203. package/dist/space-sdk/src/ui/engine/hooks.d.ts +64 -0
  204. package/dist/space-sdk/src/ui/engine/index.d.ts +14 -0
  205. package/dist/space-sdk/src/ui/engine/instance.d.ts +21 -0
  206. package/dist/space-sdk/src/ui/engine/keycapContext.d.ts +2 -0
  207. package/dist/space-sdk/src/ui/engine/registry.d.ts +77 -0
  208. package/dist/space-sdk/src/ui/engine/step.d.ts +11 -0
  209. package/dist/space-sdk/src/ui/engine/types.d.ts +54 -0
  210. package/dist/space-sdk/src/ui/fields/fieldWords.d.ts +56 -0
  211. package/dist/space-sdk/src/ui/fields/fields.d.ts +162 -0
  212. package/dist/space-sdk/src/ui/fields/index.d.ts +5 -0
  213. package/dist/space-sdk/src/ui/fields/useFieldDraft.d.ts +22 -0
  214. package/dist/space-sdk/src/ui/gallery/AttachmentGallery.d.ts +53 -0
  215. package/dist/space-sdk/src/ui/gallery/ImageViewer.d.ts +27 -0
  216. package/dist/space-sdk/src/ui/gallery/galleryWords.d.ts +95 -0
  217. package/dist/space-sdk/src/ui/gallery/index.d.ts +5 -0
  218. package/dist/space-sdk/src/ui/icons.d.ts +13 -0
  219. package/dist/space-sdk/src/ui/index.d.ts +54 -0
  220. package/dist/space-sdk/src/ui/keycap/Keycap.d.ts +22 -0
  221. package/dist/space-sdk/src/ui/keycap/index.d.ts +2 -0
  222. package/dist/space-sdk/src/ui/layout/Panels.d.ts +50 -0
  223. package/dist/space-sdk/src/ui/layout/chatPanelEdge.d.ts +77 -0
  224. package/dist/space-sdk/src/ui/layout/index.d.ts +5 -0
  225. package/dist/space-sdk/src/ui/layout/panelMath.d.ts +91 -0
  226. package/dist/space-sdk/src/ui/list/ListReader.d.ts +380 -0
  227. package/dist/space-sdk/src/ui/list/Row.d.ts +145 -0
  228. package/dist/space-sdk/src/ui/list/SelectionBar.d.ts +65 -0
  229. package/dist/space-sdk/src/ui/list/index.d.ts +14 -0
  230. package/dist/space-sdk/src/ui/list/rowState.d.ts +22 -0
  231. package/dist/space-sdk/src/ui/list/useListKeys.d.ts +89 -0
  232. package/dist/space-sdk/src/ui/list/useReorder.d.ts +86 -0
  233. package/dist/space-sdk/src/ui/list/useSelection.d.ts +90 -0
  234. package/dist/space-sdk/src/ui/open/OpenAsMenu.d.ts +15 -0
  235. package/dist/space-sdk/src/ui/open/index.d.ts +6 -0
  236. package/dist/space-sdk/src/ui/open/openModeStore.d.ts +39 -0
  237. package/dist/space-sdk/src/ui/open/useOpenMode.d.ts +17 -0
  238. package/dist/space-sdk/src/ui/overlay/Menu.d.ts +88 -0
  239. package/dist/space-sdk/src/ui/overlay/Popover.d.ts +28 -0
  240. package/dist/space-sdk/src/ui/overlay/Tooltip.d.ts +19 -0
  241. package/dist/space-sdk/src/ui/overlay/index.d.ts +9 -0
  242. package/dist/space-sdk/src/ui/overlay/layerRoot.d.ts +24 -0
  243. package/dist/space-sdk/src/ui/overlay/position.d.ts +31 -0
  244. package/dist/space-sdk/src/ui/period/PeriodPicker.d.ts +30 -0
  245. package/dist/space-sdk/src/ui/period/dateFormat.d.ts +50 -0
  246. package/dist/space-sdk/src/ui/period/dateWords.d.ts +88 -0
  247. package/dist/space-sdk/src/ui/period/periods.d.ts +70 -0
  248. package/dist/space-sdk/src/ui/period/usePeriod.d.ts +23 -0
  249. package/dist/space-sdk/src/ui/present/index.d.ts +3 -0
  250. package/dist/space-sdk/src/ui/present/presentWords.d.ts +27 -0
  251. package/dist/space-sdk/src/ui/present/screens.d.ts +36 -0
  252. package/dist/space-sdk/src/ui/present/useAudienceScreen.d.ts +38 -0
  253. package/dist/space-sdk/src/ui/styles.d.ts +21 -0
  254. package/dist/space-sdk/src/ui/timeline/TimelineView.d.ts +44 -0
  255. package/dist/space-sdk/src/ui/timeline/timelineMath.d.ts +123 -0
  256. package/dist/space-sdk/src/viewPeriod.d.ts +8 -0
  257. package/dist/space-sdk/src/vite.ts +92 -0
  258. package/dist/space-sdk/src/words/actions.ts +295 -0
  259. package/dist/space-sdk/src/words/clashes.ts +158 -0
  260. package/dist/space-sdk/src/words/format.ts +136 -0
  261. package/dist/space-sdk/src/words/index.ts +13 -0
  262. package/dist/space-sdk/src/words/keymap.ts +72 -0
  263. package/dist/space-sdk/src/words/keys.ts +297 -0
  264. package/dist/space-sdk/src/words/shell.ts +46 -0
  265. package/dist/space-sdk/src/words/types.ts +65 -0
  266. package/dist/view-renderer/dist/host.css +1 -0
  267. package/dist/view-renderer/dist/host.js +2374 -0
  268. package/dist/view-renderer/dist/theme.css +73 -0
  269. package/dist/view-renderer/src/dom.js +212 -0
  270. package/dist/view-renderer/src/errors.js +8 -0
  271. package/dist/view-renderer/src/host.tsx +250 -0
  272. package/dist/view-renderer/src/index.js +4 -0
  273. package/dist/view-renderer/src/png.js +213 -0
  274. package/dist/view-renderer/src/pool.js +110 -0
  275. package/dist/view-renderer/src/remote-broker.js +59 -0
  276. package/dist/view-renderer/src/renderer.js +271 -0
  277. package/package.json +21 -11
  278. package/skills/notis-cli/AGENT_INSTRUCTIONS.md +63 -12
  279. package/skills/notis-onboarding/BRIEF.md +14 -9
  280. package/skills/notis-query/cli.md +1 -1
  281. package/skills/notis-spaces/cli.md +540 -0
  282. package/src/cli.js +8 -8
  283. package/src/command-specs/agents.js +121 -8
  284. package/src/command-specs/auth.js +3 -3
  285. package/src/command-specs/diagnostics.js +97 -20
  286. package/src/command-specs/documents.js +158 -0
  287. package/src/command-specs/files.js +29 -0
  288. package/src/command-specs/handover.js +331 -79
  289. package/src/command-specs/helpers.js +50 -40
  290. package/src/command-specs/index.js +14 -5
  291. package/src/command-specs/meta.js +38 -9
  292. package/src/command-specs/onboarding.js +1 -1
  293. package/src/command-specs/profile.js +42 -1
  294. package/src/command-specs/skills.js +314 -9
  295. package/src/command-specs/smoke.js +16 -4
  296. package/src/command-specs/spaces.js +783 -0
  297. package/src/command-specs/tools.js +118 -21
  298. package/src/command-specs/version-history.js +20 -0
  299. package/src/command-specs/views.js +69 -0
  300. package/src/runtime/agent-browser.js +178 -267
  301. package/src/runtime/agent-setup.js +172 -13
  302. package/src/runtime/app-boundary-validator.js +338 -2
  303. package/src/runtime/app-data-literals.js +82 -0
  304. package/src/runtime/app-platform.js +750 -1568
  305. package/src/runtime/auth-recovery.js +17 -0
  306. package/src/runtime/base-skills.d.ts +11 -0
  307. package/src/runtime/base-skills.js +100 -3
  308. package/src/runtime/computer-identity.d.ts +30 -0
  309. package/src/runtime/computer-identity.js +151 -0
  310. package/src/runtime/config-lock.js +135 -0
  311. package/src/runtime/confined-space-build.js +74 -0
  312. package/src/runtime/confined-space-verify.js +49 -0
  313. package/src/runtime/delegated-context.js +5 -21
  314. package/src/runtime/native-record-files.js +47 -0
  315. package/src/runtime/oauth.js +342 -31
  316. package/src/runtime/profiles.js +19 -112
  317. package/src/runtime/retired-sdk-files.js +24 -0
  318. package/src/runtime/retry.js +150 -0
  319. package/src/runtime/skill-sync/cloud-client.ts +33 -8
  320. package/src/runtime/skill-sync/index.ts +443 -106
  321. package/src/runtime/skill-sync/local-scanner.ts +248 -32
  322. package/src/runtime/skill-sync/native-identity.ts +102 -0
  323. package/src/runtime/skill-sync/native-plan.ts +70 -0
  324. package/src/runtime/skill-sync/symlink-manager.ts +66 -16
  325. package/src/runtime/skill-sync/sync-plan.ts +34 -2
  326. package/src/runtime/skill-sync/types.ts +54 -0
  327. package/src/runtime/skill-sync/write-cloud-skill.ts +4 -4
  328. package/src/runtime/skill-sync-service.js +109 -0
  329. package/src/runtime/source-root.js +32 -0
  330. package/src/runtime/space-collection-scaffold.js +107 -0
  331. package/src/runtime/space-database-structures.js +229 -0
  332. package/src/runtime/space-fixture-broker.js +68 -0
  333. package/src/runtime/space-lock.js +337 -0
  334. package/src/runtime/space-platform.js +133 -0
  335. package/src/runtime/space-source.js +290 -0
  336. package/src/runtime/space-test-server.js +354 -0
  337. package/src/runtime/space-verification.js +94 -0
  338. package/src/runtime/space-verify-browser-worker.js +51 -0
  339. package/src/runtime/space-view-manifest.js +368 -0
  340. package/src/runtime/sync-skills.d.ts +5 -1
  341. package/src/runtime/sync-skills.js +11 -2
  342. package/src/runtime/tool-confirmation.js +138 -0
  343. package/src/runtime/transport.js +118 -24
  344. package/src/runtime/upsell.js +219 -0
  345. package/src/runtime/view-render.js +49 -0
  346. package/src/skill-sync-worker-entry.js +2 -0
  347. package/src/skill-sync-worker.js +114 -0
  348. package/src/space-harness-host.ts +122 -0
  349. package/template/.harness/index.html.tmpl +9 -352
  350. package/template/app/globals.css +29 -3
  351. package/template/app/layout.tsx +2 -6
  352. package/template/components/ui/badge.tsx +7 -4
  353. package/template/components/ui/button.tsx +1 -1
  354. package/template/components/ui/native-select.tsx +24 -0
  355. package/template/notis.config.ts +4 -36
  356. package/template/package-lock.json +1800 -1867
  357. package/template/package.json +16 -14
  358. package/template/postcss.config.mjs +1 -1
  359. package/template/tailwind.config.ts +1 -6
  360. package/template/tsconfig.json +1 -0
  361. package/template/vite.config.ts +2 -2
  362. package/dist/base-skills/notis-apps/SKILL.md +0 -613
  363. package/skills/notis-apps/cli.md +0 -285
  364. package/src/command-specs/apps.js +0 -3000
  365. package/src/runtime/app-changelog.js +0 -79
  366. package/src/runtime/app-dev-consumers.js +0 -154
  367. package/src/runtime/app-dev-host-lock.js +0 -80
  368. package/src/runtime/app-dev-process-identity.js +0 -111
  369. package/src/runtime/app-dev-roots.js +0 -284
  370. package/src/runtime/app-dev-server.js +0 -1140
  371. package/src/runtime/app-dev-sessions.js +0 -185
  372. package/src/runtime/app-registry-scaffolds.js +0 -367
  373. package/src/runtime/assets/store-screenshot-dark.png +0 -0
  374. package/src/runtime/cli-mode.generated.js +0 -5
  375. package/src/runtime/cli-mode.js +0 -34
  376. package/src/runtime/store-screenshot.js +0 -143
  377. package/template/CHANGELOG.md +0 -5
  378. package/template/app/page.tsx +0 -60
  379. package/template/components/ui/card.tsx +0 -56
  380. package/template/packages/sdk/src/components/DocumentEditor.tsx +0 -103
  381. package/template/packages/sdk/src/config.ts +0 -259
  382. package/template/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
  383. package/template/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
  384. package/template/packages/sdk/src/hooks/useDocument.ts +0 -78
  385. package/template/packages/sdk/src/hooks/useNotisNavigation.ts +0 -52
  386. package/template/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
  387. package/template/packages/sdk/src/runtime.ts +0 -439
  388. package/template/packages/sdk/src/vite.ts +0 -56
  389. package/template/packages/sdk/tsconfig.json +0 -15
  390. /package/{template/packages → dist}/sdk/src/components/MultiSelectDragOverlay.tsx +0 -0
  391. /package/{template/packages → dist}/sdk/src/components/ShortcutHints.tsx +0 -0
  392. /package/{template/packages → dist}/sdk/src/hooks/useActiveResource.ts +0 -0
  393. /package/{template/packages → dist}/sdk/src/hooks/useTool.ts +0 -0
  394. /package/{template/packages → dist}/sdk/src/hooks/useTools.ts +0 -0
  395. /package/{template/packages → dist}/sdk/src/ui.ts +0 -0
@@ -0,0 +1,123 @@
1
+ /**
2
+ * TimelineView math (toolkit spec 2.11, deck round 5 "timeline"). Pure: date-fns only, no React.
3
+ *
4
+ * Days are counted from today (0 is today). Each zoom shows a fixed window around today and a fixed number of pixels
5
+ * per day, so the roadmap scrolls sideways instead of squeezing titles. Bars of a lane are packed in rows: a bar takes
6
+ * the first row whose last bar ended before it starts.
7
+ */
8
+ import type { Locale } from '../../words/types';
9
+ import { type DateInput } from '../calendar/calendarMath';
10
+ export type TimelineZoom = 'week' | 'month' | 'quarter';
11
+ export declare const TIMELINE_ZOOMS: readonly TimelineZoom[];
12
+ /** Window of each zoom: first day (from today), days shown, pixels per day. */
13
+ export declare const TIMELINE_ZOOM: Readonly<Record<TimelineZoom, {
14
+ from: number;
15
+ span: number;
16
+ px: number;
17
+ }>>;
18
+ /** How far `[` and `]` move the window, in days. */
19
+ export declare const TIMELINE_STEP: Readonly<Record<TimelineZoom, number>>;
20
+ export declare const TIMELINE_ROW_H = 36;
21
+ export declare const TIMELINE_BAR_TOP = 6;
22
+ export declare const TIMELINE_TRACK_PAD = 12;
23
+ /** A bar cut by the window edge needs this many visible days to carry its title; thinner slivers are left out. */
24
+ export declare const TIMELINE_MIN_VISIBLE_DAYS = 4;
25
+ /** A tick label this close to today (share of the window) hides behind the Today chip. */
26
+ export declare const TIMELINE_TODAY_GAP = 0.045;
27
+ export interface TimelineWindow {
28
+ zoom: TimelineZoom;
29
+ /** First day shown, in days from today. */
30
+ from: number;
31
+ span: number;
32
+ px: number;
33
+ }
34
+ export declare function timelineWindow(zoom: TimelineZoom, offset?: number): TimelineWindow;
35
+ /** `min-width` of the timeline: the lane labels (9rem) plus the days. */
36
+ export declare function timelineMinWidth(win: TimelineWindow): string;
37
+ /** Days from today to a day. */
38
+ export declare function dayIndex(date: Date, today: Date): number;
39
+ /** Start day and end day (exclusive) from today. No end, or an end before the start, gives one day. */
40
+ export interface DaySpan {
41
+ s: number;
42
+ e: number;
43
+ }
44
+ export declare function itemSpan(start: DateInput, end: DateInput, today: Date): DaySpan | null;
45
+ export declare function barVisibleDays(span: DaySpan, win: TimelineWindow): number;
46
+ /**
47
+ * The bar shows when it is wholly inside the window, or when its visible part is at least four days (the deck's
48
+ * rule for slivers at the edges).
49
+ */
50
+ export declare function inView(span: DaySpan, win: TimelineWindow): boolean;
51
+ /** Packs bars into rows, sorted by start (stable). */
52
+ export declare function packRows<B extends {
53
+ span: DaySpan;
54
+ }>(bars: readonly B[]): {
55
+ bars: Array<B & {
56
+ row: number;
57
+ }>;
58
+ rows: number;
59
+ };
60
+ export interface BarGeometry {
61
+ /** Percent of the track. */
62
+ left: number;
63
+ width: number;
64
+ top: number;
65
+ cutLeft: boolean;
66
+ cutRight: boolean;
67
+ }
68
+ export declare function barGeometry(span: DaySpan, win: TimelineWindow, row: number): BarGeometry;
69
+ export declare function trackHeight(rows: number): number;
70
+ /** Percent of the track for a day from today. */
71
+ export declare function dayPercent(day: number, win: TimelineWindow): number;
72
+ export declare function todayInWindow(win: TimelineWindow): boolean;
73
+ export interface TimelineTick {
74
+ /** Days from today. */
75
+ day: number;
76
+ date: Date;
77
+ label: string;
78
+ percent: number;
79
+ /** False when the label would sit under the Today chip (the grid line still draws). */
80
+ showLabel: boolean;
81
+ }
82
+ /** Quarter: the first of each month ("Nov" / "Nov."). Week and month: every Monday ("Oct 19" / "19 oct."). */
83
+ export declare function timelineTicks(win: TimelineWindow, today: Date, locale: Locale): TimelineTick[];
84
+ export interface TimelineLaneInfo {
85
+ id: string;
86
+ label: string;
87
+ }
88
+ export interface LaidBar<T> {
89
+ item: T;
90
+ id: string;
91
+ span: DaySpan;
92
+ row: number;
93
+ geometry: BarGeometry;
94
+ }
95
+ export interface LaidLane<T> extends TimelineLaneInfo {
96
+ bars: Array<LaidBar<T>>;
97
+ rows: number;
98
+ height: number;
99
+ /** Items of the lane in the window. */
100
+ count: number;
101
+ }
102
+ export interface TimelineLayoutInput<T> {
103
+ items: readonly T[];
104
+ getId: (item: T) => string;
105
+ getStart: (item: T) => DateInput;
106
+ getEnd: (item: T) => DateInput;
107
+ lane: (item: T) => TimelineLaneInfo;
108
+ /** Lane order; lanes not listed follow in the order they first appear. Lanes listed here show even when empty. */
109
+ lanes?: readonly TimelineLaneInfo[];
110
+ win: TimelineWindow;
111
+ today: Date;
112
+ }
113
+ /** Lanes with packed bars, plus `order`: the bar ids lane by lane, by start (what J and K walk). */
114
+ export declare function layoutTimeline<T>(input: TimelineLayoutInput<T>): {
115
+ lanes: Array<LaidLane<T>>;
116
+ order: string[];
117
+ undated: T[];
118
+ };
119
+ /**
120
+ * Where T puts the cursor once the window is back on today: the first bar (in reading order) running today, else the
121
+ * bar starting soonest after today, else the last bar.
122
+ */
123
+ export declare function timelineTodayCursor<T>(lanes: ReadonlyArray<LaidLane<T>>): string | null;
@@ -0,0 +1,8 @@
1
+ /** Inclusive calendar ranges; Monday starts a week. Matches the server's shown reader. */
2
+ export declare const VIEW_PERIODS: readonly ['today', 'tomorrow', 'this_week', 'last_week', 'this_month', 'last_month'];
3
+ export declare function isViewDate(value: string): boolean;
4
+ export declare function isViewPeriod(value: string): boolean;
5
+ export declare function resolveViewPeriod(value: string, timeZone?: string, now?: Date): {
6
+ start: string;
7
+ end: string;
8
+ };
@@ -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
+ }