@notis_ai/cli 0.2.14 → 0.2.16

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 (299) hide show
  1. package/README.md +83 -21
  2. package/dist/agent-hooks/notis-agent-hook.mjs +20168 -0
  3. package/{skills → dist/base-skills}/notis-apps/SKILL.md +167 -72
  4. package/{skills → dist/base-skills}/notis-cli/SKILL.md +86 -28
  5. package/dist/base-skills/notis-query/SKILL.md +705 -0
  6. package/dist/skill-sync/index.js +1528 -0
  7. package/dist/skill-sync/index.js.map +7 -0
  8. package/package.json +5 -2
  9. package/skills/notis-apps/cli.md +39 -18
  10. package/skills/notis-cli/AGENT_INSTRUCTIONS.md +39 -0
  11. package/skills/notis-onboarding/BRIEF.md +16 -0
  12. package/src/agent-hook-entry.js +5 -0
  13. package/src/cli.js +23 -14
  14. package/src/command-specs/agents.js +392 -0
  15. package/src/command-specs/apps.js +1021 -424
  16. package/src/command-specs/auth.js +63 -10
  17. package/src/command-specs/index.js +6 -0
  18. package/src/command-specs/onboarding.js +69 -5
  19. package/src/command-specs/skills.js +56 -0
  20. package/src/runtime/agent-memory-state.js +126 -0
  21. package/src/runtime/agent-setup.js +383 -0
  22. package/src/runtime/app-dev-consumers.js +154 -0
  23. package/src/runtime/app-dev-host-lock.js +80 -0
  24. package/src/runtime/app-dev-process-identity.js +111 -0
  25. package/src/runtime/app-dev-roots.js +284 -0
  26. package/src/runtime/app-dev-server.js +447 -29
  27. package/src/runtime/app-dev-sessions.js +105 -145
  28. package/src/runtime/app-platform.js +1314 -166
  29. package/src/runtime/app-registry-scaffolds.js +367 -0
  30. package/src/runtime/base-skills.d.ts +20 -0
  31. package/src/runtime/base-skills.js +167 -0
  32. package/src/runtime/login-listener.js +15 -0
  33. package/src/runtime/oauth.js +1506 -141
  34. package/src/runtime/output.js +7 -0
  35. package/src/runtime/ports.js +16 -0
  36. package/src/runtime/profiles.js +19 -16
  37. package/src/runtime/skill-sync/cloud-client.ts +96 -0
  38. package/src/runtime/skill-sync/index.ts +644 -0
  39. package/src/runtime/skill-sync/local-scanner.ts +1046 -0
  40. package/src/runtime/skill-sync/symlink-manager.ts +383 -0
  41. package/src/runtime/skill-sync/sync-plan.ts +22 -0
  42. package/src/runtime/skill-sync/types.ts +103 -0
  43. package/src/runtime/skill-sync/write-cloud-skill.ts +50 -0
  44. package/src/runtime/store-screenshot.js +6 -1
  45. package/src/runtime/sync-skills.d.ts +37 -0
  46. package/src/runtime/sync-skills.js +231 -0
  47. package/template/app/layout.tsx +5 -1
  48. package/template/notis.config.ts +1 -0
  49. package/template/packages/sdk/package.json +2 -1
  50. package/template/packages/sdk/src/components/DocumentEditor.tsx +13 -3
  51. package/template/packages/sdk/src/components/MarkdownEditor.tsx +121 -0
  52. package/template/packages/sdk/src/components/MultiSelectActionBar.tsx +43 -119
  53. package/template/packages/sdk/src/components/MultiSelectCheckbox.tsx +5 -1
  54. package/template/packages/sdk/src/components/MultiSelectDragOverlay.tsx +1 -1
  55. package/template/packages/sdk/src/components/NotisSelectionBoundary.tsx +55 -0
  56. package/template/packages/sdk/src/components/ShortcutHints.tsx +56 -0
  57. package/template/packages/sdk/src/config.ts +25 -0
  58. package/template/packages/sdk/src/documents.ts +7 -1
  59. package/template/packages/sdk/src/hooks/useActiveResource.ts +19 -0
  60. package/template/packages/sdk/src/hooks/useCollectionInteractions.ts +726 -0
  61. package/template/packages/sdk/src/hooks/useHandover.ts +3 -0
  62. package/template/packages/sdk/src/hooks/useMultiSelect.ts +44 -482
  63. package/template/packages/sdk/src/hooks/useNotis.ts +3 -0
  64. package/template/packages/sdk/src/hooks/useNotisNavigation.ts +7 -4
  65. package/template/packages/sdk/src/hooks/useTool.ts +5 -4
  66. package/template/packages/sdk/src/index.ts +47 -2
  67. package/template/packages/sdk/src/interactions/actions.ts +46 -0
  68. package/template/packages/sdk/src/interactions/shortcuts.tsx +634 -0
  69. package/template/packages/sdk/src/interactions.ts +41 -0
  70. package/template/packages/sdk/src/provider.tsx +2 -1
  71. package/template/packages/sdk/src/runtime.ts +92 -4
  72. package/dist/scaffolds/notis-database/CHANGELOG.md +0 -5
  73. package/dist/scaffolds/notis-database/app/globals.css +0 -44
  74. package/dist/scaffolds/notis-database/app/layout.tsx +0 -6
  75. package/dist/scaffolds/notis-database/app/page.tsx +0 -1088
  76. package/dist/scaffolds/notis-database/components/ui/badge.tsx +0 -28
  77. package/dist/scaffolds/notis-database/components/ui/button.tsx +0 -53
  78. package/dist/scaffolds/notis-database/components/ui/card.tsx +0 -56
  79. package/dist/scaffolds/notis-database/components/ui/table.tsx +0 -120
  80. package/dist/scaffolds/notis-database/components.json +0 -20
  81. package/dist/scaffolds/notis-database/index.html +0 -12
  82. package/dist/scaffolds/notis-database/lib/types.ts +0 -132
  83. package/dist/scaffolds/notis-database/lib/utils.ts +0 -6
  84. package/dist/scaffolds/notis-database/metadata/screenshot-1.png +0 -0
  85. package/dist/scaffolds/notis-database/metadata/screenshot-2.png +0 -0
  86. package/dist/scaffolds/notis-database/metadata/screenshot-3.png +0 -0
  87. package/dist/scaffolds/notis-database/metadata/screenshot-4.png +0 -0
  88. package/dist/scaffolds/notis-database/metadata/screenshot-5.png +0 -0
  89. package/dist/scaffolds/notis-database/metadata/screenshot-fixtures.json +0 -1839
  90. package/dist/scaffolds/notis-database/notis.config.ts +0 -73
  91. package/dist/scaffolds/notis-database/package-lock.json +0 -3935
  92. package/dist/scaffolds/notis-database/package.json +0 -32
  93. package/dist/scaffolds/notis-database/packages/sdk/package.json +0 -36
  94. package/dist/scaffolds/notis-database/packages/sdk/src/components/DocumentEditor.tsx +0 -93
  95. package/dist/scaffolds/notis-database/packages/sdk/src/components/Markdown.tsx +0 -60
  96. package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
  97. package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
  98. package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
  99. package/dist/scaffolds/notis-database/packages/sdk/src/config.ts +0 -234
  100. package/dist/scaffolds/notis-database/packages/sdk/src/documents.ts +0 -250
  101. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useBackend.ts +0 -41
  102. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
  103. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
  104. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
  105. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocument.ts +0 -78
  106. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocuments.ts +0 -121
  107. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useHandover.ts +0 -75
  108. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
  109. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useNotis.ts +0 -34
  110. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
  111. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTool.ts +0 -64
  112. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTools.ts +0 -56
  113. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
  114. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
  115. package/dist/scaffolds/notis-database/packages/sdk/src/index.ts +0 -100
  116. package/dist/scaffolds/notis-database/packages/sdk/src/provider.tsx +0 -43
  117. package/dist/scaffolds/notis-database/packages/sdk/src/runtime.ts +0 -351
  118. package/dist/scaffolds/notis-database/packages/sdk/src/styles.css +0 -186
  119. package/dist/scaffolds/notis-database/packages/sdk/src/ui.ts +0 -15
  120. package/dist/scaffolds/notis-database/packages/sdk/src/vite.ts +0 -56
  121. package/dist/scaffolds/notis-database/packages/sdk/tsconfig.json +0 -15
  122. package/dist/scaffolds/notis-database/postcss.config.mjs +0 -8
  123. package/dist/scaffolds/notis-database/src/dev-main.tsx +0 -23
  124. package/dist/scaffolds/notis-database/src/mock-runtime.ts +0 -561
  125. package/dist/scaffolds/notis-database/tailwind.config.ts +0 -59
  126. package/dist/scaffolds/notis-database/tsconfig.json +0 -23
  127. package/dist/scaffolds/notis-database/vite.config.ts +0 -22
  128. package/dist/scaffolds/notis-journal/CHANGELOG.md +0 -29
  129. package/dist/scaffolds/notis-journal/app/globals.css +0 -37
  130. package/dist/scaffolds/notis-journal/app/insights/page.tsx +0 -513
  131. package/dist/scaffolds/notis-journal/app/journal-core.tsx +0 -362
  132. package/dist/scaffolds/notis-journal/app/journal-ui.tsx +0 -337
  133. package/dist/scaffolds/notis-journal/app/layout.tsx +0 -6
  134. package/dist/scaffolds/notis-journal/app/page.tsx +0 -486
  135. package/dist/scaffolds/notis-journal/components/ui/badge.tsx +0 -28
  136. package/dist/scaffolds/notis-journal/components/ui/button.tsx +0 -53
  137. package/dist/scaffolds/notis-journal/components/ui/card.tsx +0 -56
  138. package/dist/scaffolds/notis-journal/components.json +0 -20
  139. package/dist/scaffolds/notis-journal/index.html +0 -12
  140. package/dist/scaffolds/notis-journal/lib/utils.ts +0 -6
  141. package/dist/scaffolds/notis-journal/metadata/screenshot-1.png +0 -0
  142. package/dist/scaffolds/notis-journal/metadata/screenshot-2.png +0 -0
  143. package/dist/scaffolds/notis-journal/metadata/screenshot-3.png +0 -0
  144. package/dist/scaffolds/notis-journal/metadata/screenshot-4.png +0 -0
  145. package/dist/scaffolds/notis-journal/metadata/screenshot-5.png +0 -0
  146. package/dist/scaffolds/notis-journal/metadata/screenshot-6.png +0 -0
  147. package/dist/scaffolds/notis-journal/metadata/screenshot-fixtures.json +0 -132
  148. package/dist/scaffolds/notis-journal/notis.config.ts +0 -93
  149. package/dist/scaffolds/notis-journal/package-lock.json +0 -4615
  150. package/dist/scaffolds/notis-journal/package.json +0 -34
  151. package/dist/scaffolds/notis-journal/packages/sdk/package.json +0 -36
  152. package/dist/scaffolds/notis-journal/packages/sdk/src/components/DocumentEditor.tsx +0 -93
  153. package/dist/scaffolds/notis-journal/packages/sdk/src/components/Markdown.tsx +0 -60
  154. package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
  155. package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
  156. package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
  157. package/dist/scaffolds/notis-journal/packages/sdk/src/config.ts +0 -234
  158. package/dist/scaffolds/notis-journal/packages/sdk/src/documents.ts +0 -250
  159. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useBackend.ts +0 -41
  160. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
  161. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
  162. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
  163. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDocument.ts +0 -78
  164. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDocuments.ts +0 -121
  165. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useHandover.ts +0 -75
  166. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
  167. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useNotis.ts +0 -34
  168. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
  169. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTool.ts +0 -64
  170. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTools.ts +0 -56
  171. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
  172. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
  173. package/dist/scaffolds/notis-journal/packages/sdk/src/index.ts +0 -100
  174. package/dist/scaffolds/notis-journal/packages/sdk/src/provider.tsx +0 -43
  175. package/dist/scaffolds/notis-journal/packages/sdk/src/runtime.ts +0 -351
  176. package/dist/scaffolds/notis-journal/packages/sdk/src/styles.css +0 -186
  177. package/dist/scaffolds/notis-journal/packages/sdk/src/ui.ts +0 -15
  178. package/dist/scaffolds/notis-journal/packages/sdk/src/vite.ts +0 -56
  179. package/dist/scaffolds/notis-journal/packages/sdk/tsconfig.json +0 -15
  180. package/dist/scaffolds/notis-journal/postcss.config.mjs +0 -8
  181. package/dist/scaffolds/notis-journal/skills/journal-onboarding/SKILL.md +0 -120
  182. package/dist/scaffolds/notis-journal/src/dev-main.tsx +0 -58
  183. package/dist/scaffolds/notis-journal/src/mock-runtime.ts +0 -199
  184. package/dist/scaffolds/notis-journal/tailwind.config.ts +0 -58
  185. package/dist/scaffolds/notis-journal/tsconfig.json +0 -23
  186. package/dist/scaffolds/notis-journal/vite.config.ts +0 -10
  187. package/dist/scaffolds/notis-notes/CHANGELOG.md +0 -5
  188. package/dist/scaffolds/notis-notes/app/globals.css +0 -3
  189. package/dist/scaffolds/notis-notes/app/layout.tsx +0 -6
  190. package/dist/scaffolds/notis-notes/app/page.tsx +0 -1943
  191. package/dist/scaffolds/notis-notes/app/phosphor-icons.ts +0 -596
  192. package/dist/scaffolds/notis-notes/components/ui/badge.tsx +0 -28
  193. package/dist/scaffolds/notis-notes/components/ui/button.tsx +0 -53
  194. package/dist/scaffolds/notis-notes/components/ui/card.tsx +0 -56
  195. package/dist/scaffolds/notis-notes/components.json +0 -20
  196. package/dist/scaffolds/notis-notes/lib/utils.ts +0 -6
  197. package/dist/scaffolds/notis-notes/lib/visible-properties.ts +0 -144
  198. package/dist/scaffolds/notis-notes/metadata/screenshot-1.png +0 -0
  199. package/dist/scaffolds/notis-notes/metadata/screenshot-2.png +0 -0
  200. package/dist/scaffolds/notis-notes/metadata/screenshot-3.png +0 -0
  201. package/dist/scaffolds/notis-notes/metadata/screenshot-4.png +0 -0
  202. package/dist/scaffolds/notis-notes/metadata/screenshot-5.png +0 -0
  203. package/dist/scaffolds/notis-notes/metadata/screenshot-fixtures.json +0 -752
  204. package/dist/scaffolds/notis-notes/notis.config.ts +0 -80
  205. package/dist/scaffolds/notis-notes/package-lock.json +0 -4636
  206. package/dist/scaffolds/notis-notes/package.json +0 -35
  207. package/dist/scaffolds/notis-notes/packages/sdk/package.json +0 -36
  208. package/dist/scaffolds/notis-notes/packages/sdk/src/components/DocumentEditor.tsx +0 -93
  209. package/dist/scaffolds/notis-notes/packages/sdk/src/components/Markdown.tsx +0 -60
  210. package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
  211. package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
  212. package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
  213. package/dist/scaffolds/notis-notes/packages/sdk/src/config.ts +0 -234
  214. package/dist/scaffolds/notis-notes/packages/sdk/src/documents.ts +0 -250
  215. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useBackend.ts +0 -41
  216. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
  217. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
  218. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
  219. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocument.ts +0 -78
  220. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocuments.ts +0 -121
  221. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useHandover.ts +0 -75
  222. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
  223. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotis.ts +0 -34
  224. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
  225. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTool.ts +0 -64
  226. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTools.ts +0 -56
  227. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
  228. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
  229. package/dist/scaffolds/notis-notes/packages/sdk/src/index.ts +0 -100
  230. package/dist/scaffolds/notis-notes/packages/sdk/src/provider.tsx +0 -43
  231. package/dist/scaffolds/notis-notes/packages/sdk/src/runtime.ts +0 -351
  232. package/dist/scaffolds/notis-notes/packages/sdk/src/styles.css +0 -186
  233. package/dist/scaffolds/notis-notes/packages/sdk/src/ui.ts +0 -15
  234. package/dist/scaffolds/notis-notes/packages/sdk/src/vite.ts +0 -56
  235. package/dist/scaffolds/notis-notes/packages/sdk/tsconfig.json +0 -15
  236. package/dist/scaffolds/notis-notes/postcss.config.mjs +0 -8
  237. package/dist/scaffolds/notis-notes/tailwind.config.ts +0 -58
  238. package/dist/scaffolds/notis-notes/tsconfig.json +0 -23
  239. package/dist/scaffolds/notis-notes/vite.config.ts +0 -10
  240. package/dist/scaffolds/notis-random/CHANGELOG.md +0 -15
  241. package/dist/scaffolds/notis-random/README.md +0 -33
  242. package/dist/scaffolds/notis-random/app/globals.css +0 -11
  243. package/dist/scaffolds/notis-random/app/history/page.tsx +0 -67
  244. package/dist/scaffolds/notis-random/app/layout.tsx +0 -7
  245. package/dist/scaffolds/notis-random/app/page.tsx +0 -289
  246. package/dist/scaffolds/notis-random/components/ui/button.tsx +0 -50
  247. package/dist/scaffolds/notis-random/components/ui/card.tsx +0 -16
  248. package/dist/scaffolds/notis-random/components/ui/input.tsx +0 -23
  249. package/dist/scaffolds/notis-random/components.json +0 -20
  250. package/dist/scaffolds/notis-random/index.html +0 -12
  251. package/dist/scaffolds/notis-random/lib/notis-tools.ts +0 -128
  252. package/dist/scaffolds/notis-random/lib/rng.ts +0 -202
  253. package/dist/scaffolds/notis-random/lib/roll-record.ts +0 -189
  254. package/dist/scaffolds/notis-random/lib/utils.ts +0 -25
  255. package/dist/scaffolds/notis-random/metadata/screenshot-1.png +0 -0
  256. package/dist/scaffolds/notis-random/metadata/screenshot-2.png +0 -0
  257. package/dist/scaffolds/notis-random/metadata/screenshot-3.png +0 -0
  258. package/dist/scaffolds/notis-random/metadata/screenshot-4.png +0 -0
  259. package/dist/scaffolds/notis-random/metadata/screenshot-5.png +0 -0
  260. package/dist/scaffolds/notis-random/metadata/screenshot-fixtures.json +0 -753
  261. package/dist/scaffolds/notis-random/notis.config.ts +0 -86
  262. package/dist/scaffolds/notis-random/package-lock.json +0 -4513
  263. package/dist/scaffolds/notis-random/package.json +0 -36
  264. package/dist/scaffolds/notis-random/packages/sdk/package.json +0 -36
  265. package/dist/scaffolds/notis-random/packages/sdk/src/components/DocumentEditor.tsx +0 -93
  266. package/dist/scaffolds/notis-random/packages/sdk/src/components/Markdown.tsx +0 -60
  267. package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
  268. package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
  269. package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
  270. package/dist/scaffolds/notis-random/packages/sdk/src/config.ts +0 -234
  271. package/dist/scaffolds/notis-random/packages/sdk/src/documents.ts +0 -250
  272. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useBackend.ts +0 -41
  273. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
  274. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
  275. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
  276. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocument.ts +0 -78
  277. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocuments.ts +0 -121
  278. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useHandover.ts +0 -75
  279. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
  280. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotis.ts +0 -34
  281. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
  282. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTool.ts +0 -64
  283. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTools.ts +0 -56
  284. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
  285. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
  286. package/dist/scaffolds/notis-random/packages/sdk/src/index.ts +0 -100
  287. package/dist/scaffolds/notis-random/packages/sdk/src/provider.tsx +0 -43
  288. package/dist/scaffolds/notis-random/packages/sdk/src/runtime.ts +0 -351
  289. package/dist/scaffolds/notis-random/packages/sdk/src/styles.css +0 -186
  290. package/dist/scaffolds/notis-random/packages/sdk/src/ui.ts +0 -15
  291. package/dist/scaffolds/notis-random/packages/sdk/src/vite.ts +0 -56
  292. package/dist/scaffolds/notis-random/packages/sdk/tsconfig.json +0 -15
  293. package/dist/scaffolds/notis-random/postcss.config.mjs +0 -6
  294. package/dist/scaffolds/notis-random/src/dev-main.tsx +0 -70
  295. package/dist/scaffolds/notis-random/src/mock-runtime.ts +0 -129
  296. package/dist/scaffolds/notis-random/tailwind.config.ts +0 -50
  297. package/dist/scaffolds/notis-random/tsconfig.json +0 -23
  298. package/dist/scaffolds/notis-random/vite.config.ts +0 -11
  299. package/dist/scaffolds.json +0 -49
@@ -0,0 +1,634 @@
1
+ 'use client';
2
+
3
+ import React, {
4
+ createContext,
5
+ useCallback,
6
+ useContext,
7
+ useEffect,
8
+ useMemo,
9
+ useRef,
10
+ useState,
11
+ type ReactNode,
12
+ } from 'react';
13
+
14
+ export type ShortcutScope = 'app' | 'route' | 'collection' | 'detail' | 'modal';
15
+
16
+ export const SHORTCUT_SCOPE_PRIORITY: Record<ShortcutScope, number> = {
17
+ app: 0,
18
+ route: 100,
19
+ collection: 200,
20
+ detail: 300,
21
+ modal: 400,
22
+ };
23
+
24
+ export interface ShortcutDefinition {
25
+ id: string;
26
+ /** A chord (`Mod+A`) or timed sequence (`G I`). */
27
+ keys: string | string[];
28
+ label?: string;
29
+ enabled?: boolean;
30
+ allowInEditable?: boolean;
31
+ allowRepeat?: boolean;
32
+ preventDefault?: boolean;
33
+ stopPropagation?: boolean;
34
+ onTrigger: (event: KeyboardEvent) => void | Promise<void>;
35
+ }
36
+
37
+ export interface UseShortcutsOptions {
38
+ enabled?: boolean;
39
+ scope?: ShortcutScope;
40
+ /** Added to the scope priority for local precedence within a scope. */
41
+ priority?: number;
42
+ sequenceTimeoutMs?: number;
43
+ /** Internal owner used to route collection shortcuts to the last interacted collection. */
44
+ collectionOwnerId?: string;
45
+ }
46
+
47
+ interface ShortcutRegistration {
48
+ id: number;
49
+ order: number;
50
+ scope: ShortcutScope;
51
+ priority: number;
52
+ enabled: boolean;
53
+ sequenceTimeoutMs: number;
54
+ collectionOwnerId?: string;
55
+ getShortcuts: () => ShortcutDefinition[];
56
+ }
57
+
58
+ interface ShortcutRegistry {
59
+ register: (registration: Omit<ShortcutRegistration, 'id' | 'order'>) => () => void;
60
+ }
61
+
62
+ const SHORTCUT_CONTEXT_SYMBOL = Symbol.for('notis.sdk.shortcut_context');
63
+ type ShortcutContextGlobal = typeof globalThis & {
64
+ [SHORTCUT_CONTEXT_SYMBOL]?: React.Context<ShortcutRegistry | null>;
65
+ };
66
+
67
+ function getShortcutContext(): React.Context<ShortcutRegistry | null> {
68
+ const scope = globalThis as ShortcutContextGlobal;
69
+ if (!scope[SHORTCUT_CONTEXT_SYMBOL]) {
70
+ scope[SHORTCUT_CONTEXT_SYMBOL] = createContext<ShortcutRegistry | null>(null);
71
+ }
72
+ return scope[SHORTCUT_CONTEXT_SYMBOL]!;
73
+ }
74
+
75
+ const ShortcutContext = getShortcutContext();
76
+
77
+ const SHORTCUT_COLLECTION_STATE_SYMBOL = Symbol.for('notis.sdk.shortcut_collection_state');
78
+ type ShortcutCollectionGlobal = typeof globalThis & {
79
+ [SHORTCUT_COLLECTION_STATE_SYMBOL]?: { activeOwnerId: string | null; nextOwnerId: number };
80
+ };
81
+
82
+ function getShortcutCollectionState() {
83
+ const scope = globalThis as ShortcutCollectionGlobal;
84
+ if (!scope[SHORTCUT_COLLECTION_STATE_SYMBOL]) {
85
+ scope[SHORTCUT_COLLECTION_STATE_SYMBOL] = { activeOwnerId: null, nextOwnerId: 1 };
86
+ }
87
+ return scope[SHORTCUT_COLLECTION_STATE_SYMBOL]!;
88
+ }
89
+
90
+ export function createShortcutCollectionOwner(): string {
91
+ const state = getShortcutCollectionState();
92
+ return `collection-${state.nextOwnerId++}`;
93
+ }
94
+
95
+ export function activateShortcutCollection(ownerId: string): void {
96
+ getShortcutCollectionState().activeOwnerId = ownerId;
97
+ }
98
+
99
+ interface ParsedChord {
100
+ key: string;
101
+ mod: boolean;
102
+ shift: boolean;
103
+ alt: boolean;
104
+ }
105
+
106
+ interface ParsedShortcut {
107
+ definition: ShortcutDefinition;
108
+ sequence: ParsedChord[];
109
+ }
110
+
111
+ interface SequenceState {
112
+ chords: string[];
113
+ at: number;
114
+ }
115
+
116
+ function normalizeKey(key: string): string {
117
+ const lower = key.trim().toLowerCase();
118
+ if (lower === 'esc') return 'escape';
119
+ if (lower === 'return') return 'enter';
120
+ if (lower === 'space') return ' ';
121
+ if (lower === 'arrowup' || lower === 'up') return 'arrowup';
122
+ if (lower === 'arrowdown' || lower === 'down') return 'arrowdown';
123
+ if (lower === 'arrowleft' || lower === 'left') return 'arrowleft';
124
+ if (lower === 'arrowright' || lower === 'right') return 'arrowright';
125
+ return lower;
126
+ }
127
+
128
+ function parseChord(raw: string): ParsedChord | null {
129
+ const parts = raw.split('+').map((part) => part.trim()).filter(Boolean);
130
+ if (parts.length === 0) return null;
131
+ let key = '';
132
+ let mod = false;
133
+ let shift = false;
134
+ let alt = false;
135
+ for (const part of parts) {
136
+ const lower = part.toLowerCase();
137
+ if (lower === 'mod' || lower === 'cmd' || lower === 'ctrl' || lower === 'meta') {
138
+ mod = true;
139
+ } else if (lower === 'shift') {
140
+ shift = true;
141
+ } else if (lower === 'alt' || lower === 'option') {
142
+ alt = true;
143
+ } else {
144
+ key = normalizeKey(part);
145
+ }
146
+ }
147
+ return key ? { key, mod, shift, alt } : null;
148
+ }
149
+
150
+ function parseShortcut(definition: ShortcutDefinition): ParsedShortcut[] {
151
+ const values = Array.isArray(definition.keys) ? definition.keys : [definition.keys];
152
+ return values.flatMap((value) => {
153
+ const sequence = value
154
+ .trim()
155
+ .split(/\s+/)
156
+ .map(parseChord)
157
+ .filter((chord): chord is ParsedChord => chord !== null);
158
+ return sequence.length > 0 ? [{ definition, sequence }] : [];
159
+ });
160
+ }
161
+
162
+ function chordToken(chord: ParsedChord): string {
163
+ return `${chord.mod ? 'm' : '-'}${chord.shift ? 's' : '-'}${chord.alt ? 'a' : '-'}:${chord.key}`;
164
+ }
165
+
166
+ function eventChord(event: KeyboardEvent): ParsedChord {
167
+ let key = normalizeKey(event.key);
168
+ let shift = event.shiftKey;
169
+ if (key === '3' && shift) key = '#';
170
+ if (key === '#' || key === '?') shift = false;
171
+ return {
172
+ key,
173
+ mod: Boolean(event.metaKey || event.ctrlKey),
174
+ shift,
175
+ alt: event.altKey,
176
+ };
177
+ }
178
+
179
+ export function isEditableShortcutEvent(event: Event): boolean {
180
+ const path = event.composedPath?.() ?? [event.target];
181
+ return path.some((target) => {
182
+ if (!(target instanceof HTMLElement)) return false;
183
+ if (target.isContentEditable || target.getAttribute('contenteditable') === 'true') return true;
184
+ const tag = target.tagName;
185
+ if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || tag === 'BUTTON' || tag === 'SUMMARY') {
186
+ return true;
187
+ }
188
+ if (tag === 'A' && target.hasAttribute('href')) return true;
189
+ return ['button', 'link', 'menuitem', 'option', 'switch', 'tab'].includes(
190
+ target.getAttribute('role') || '',
191
+ );
192
+ });
193
+ }
194
+
195
+ function matchPrefix(tokens: string[], candidate: ParsedShortcut): boolean {
196
+ if (tokens.length > candidate.sequence.length) return false;
197
+ return tokens.every((token, index) => token === chordToken(candidate.sequence[index]!));
198
+ }
199
+
200
+ function consumeShortcut(event: KeyboardEvent, definition: ShortcutDefinition): void {
201
+ if (definition.preventDefault !== false) event.preventDefault();
202
+ if (definition.stopPropagation !== false) {
203
+ event.stopPropagation();
204
+ event.stopImmediatePropagation?.();
205
+ }
206
+ void definition.onTrigger(event);
207
+ }
208
+
209
+ export function ShortcutProvider({ children }: { children: ReactNode }) {
210
+ const parentRegistry = useContext(ShortcutContext);
211
+ if (parentRegistry) return <>{children}</>;
212
+ return <ShortcutProviderRoot>{children}</ShortcutProviderRoot>;
213
+ }
214
+
215
+ export interface ShortcutHelpEntry {
216
+ id: string;
217
+ keys: string[];
218
+ label: string;
219
+ scope: ShortcutScope;
220
+ }
221
+
222
+ const HELP_SCOPE_LABELS: Record<ShortcutScope, string> = {
223
+ modal: 'Dialog',
224
+ detail: 'Detail',
225
+ collection: 'Collection',
226
+ route: 'This view',
227
+ app: 'App',
228
+ };
229
+
230
+ const HELP_SCOPE_ORDER: ShortcutScope[] = ['modal', 'detail', 'collection', 'route', 'app'];
231
+
232
+ function applicableRegistrations(
233
+ registrations: Iterable<ShortcutRegistration>,
234
+ ): ShortcutRegistration[] {
235
+ const ordered = Array.from(registrations)
236
+ .filter((registration) => registration.enabled)
237
+ .sort((a, b) => b.priority - a.priority || b.order - a.order);
238
+ const activeCollectionOwner = getShortcutCollectionState().activeOwnerId;
239
+ const hasActiveCollection = Boolean(
240
+ activeCollectionOwner
241
+ && ordered.some((registration) => registration.collectionOwnerId === activeCollectionOwner),
242
+ );
243
+ return hasActiveCollection
244
+ ? ordered.filter(
245
+ (registration) => !registration.collectionOwnerId
246
+ || registration.collectionOwnerId === activeCollectionOwner,
247
+ )
248
+ : ordered;
249
+ }
250
+
251
+ function shortcutToken(raw: string): string | null {
252
+ const definition: ShortcutDefinition = { id: '', keys: raw, onTrigger: () => undefined };
253
+ const parsed = parseShortcut(definition)[0];
254
+ return parsed ? parsed.sequence.map(chordToken).join(' ') : null;
255
+ }
256
+
257
+ function collectShortcutHelpEntries(
258
+ registrations: Iterable<ShortcutRegistration>,
259
+ ): ShortcutHelpEntry[] {
260
+ const claimed = new Set<string>();
261
+ const entries: ShortcutHelpEntry[] = [];
262
+ for (const registration of applicableRegistrations(registrations)) {
263
+ for (const shortcut of registration.getShortcuts()) {
264
+ if (shortcut.enabled === false || !shortcut.label) continue;
265
+ const values = Array.isArray(shortcut.keys) ? shortcut.keys : [shortcut.keys];
266
+ const keys = values.filter((value) => {
267
+ const token = shortcutToken(value);
268
+ if (!token || claimed.has(token)) return false;
269
+ claimed.add(token);
270
+ return true;
271
+ });
272
+ if (keys.length > 0) {
273
+ entries.push({ id: shortcut.id, keys, label: shortcut.label, scope: registration.scope });
274
+ }
275
+ }
276
+ }
277
+ return entries;
278
+ }
279
+
280
+ function ShortcutProviderRoot({ children }: { children: ReactNode }) {
281
+ const registrationsRef = useRef(new Map<number, ShortcutRegistration>());
282
+ const nextIdRef = useRef(1);
283
+ const nextOrderRef = useRef(1);
284
+ const sequenceRef = useRef<SequenceState>({ chords: [], at: 0 });
285
+ const [helpOpen, setHelpOpen] = useState(false);
286
+ const helpOpenRef = useRef(false);
287
+ const [, setRegistryVersion] = useState(0);
288
+
289
+ const updateHelpOpen = useCallback((open: boolean) => {
290
+ helpOpenRef.current = open;
291
+ setHelpOpen(open);
292
+ }, []);
293
+
294
+ const register = useCallback((input: Omit<ShortcutRegistration, 'id' | 'order'>) => {
295
+ const id = nextIdRef.current++;
296
+ registrationsRef.current.set(id, {
297
+ ...input,
298
+ id,
299
+ order: nextOrderRef.current++,
300
+ });
301
+ if (helpOpenRef.current) setRegistryVersion((version) => version + 1);
302
+ return () => {
303
+ registrationsRef.current.delete(id);
304
+ if (helpOpenRef.current) setRegistryVersion((version) => version + 1);
305
+ };
306
+ }, []);
307
+
308
+ useEffect(() => {
309
+ const handleKeyDown = (event: KeyboardEvent) => {
310
+ const editable = isEditableShortcutEvent(event);
311
+ if (!editable && !event.repeat && event.key === '?' && !event.metaKey && !event.ctrlKey && !event.altKey) {
312
+ event.preventDefault();
313
+ event.stopPropagation();
314
+ event.stopImmediatePropagation?.();
315
+ updateHelpOpen(!helpOpenRef.current);
316
+ return;
317
+ }
318
+ if (helpOpenRef.current) {
319
+ if (event.key === 'Escape') {
320
+ event.preventDefault();
321
+ event.stopPropagation();
322
+ event.stopImmediatePropagation?.();
323
+ updateHelpOpen(false);
324
+ }
325
+ return;
326
+ }
327
+
328
+ const registrations = applicableRegistrations(registrationsRef.current.values());
329
+ if (registrations.length === 0) return;
330
+ const token = chordToken(eventChord(event));
331
+ const now = Date.now();
332
+
333
+ for (const registration of registrations) {
334
+ const candidates = registration.getShortcuts()
335
+ .filter((shortcut) => shortcut.enabled !== false)
336
+ .filter((shortcut) => !editable || shortcut.allowInEditable)
337
+ .filter((shortcut) => !event.repeat || shortcut.allowRepeat)
338
+ .flatMap(parseShortcut);
339
+ if (candidates.length === 0) continue;
340
+
341
+ const previous = now - sequenceRef.current.at <= registration.sequenceTimeoutMs
342
+ ? sequenceRef.current.chords
343
+ : [];
344
+ const attempts = previous.length > 0 ? [[...previous, token], [token]] : [[token]];
345
+
346
+ for (const attempt of attempts) {
347
+ const matches = candidates.filter((candidate) => matchPrefix(attempt, candidate));
348
+ if (matches.length === 0) continue;
349
+ const exact = matches.find((candidate) => candidate.sequence.length === attempt.length);
350
+ const hasLonger = matches.some((candidate) => candidate.sequence.length > attempt.length);
351
+ if (exact && !hasLonger) {
352
+ sequenceRef.current = { chords: [], at: 0 };
353
+ consumeShortcut(event, exact.definition);
354
+ return;
355
+ }
356
+ sequenceRef.current = { chords: attempt, at: now };
357
+ event.preventDefault();
358
+ return;
359
+ }
360
+ }
361
+
362
+ sequenceRef.current = { chords: [], at: 0 };
363
+ };
364
+
365
+ document.addEventListener('keydown', handleKeyDown, true);
366
+ return () => document.removeEventListener('keydown', handleKeyDown, true);
367
+ }, [updateHelpOpen]);
368
+
369
+ const registry = useMemo<ShortcutRegistry>(() => ({ register }), [register]);
370
+ const helpEntries = helpOpen ? collectShortcutHelpEntries(registrationsRef.current.values()) : [];
371
+ return (
372
+ <ShortcutContext.Provider value={registry}>
373
+ {children}
374
+ {helpOpen ? <ShortcutHelpDialog entries={helpEntries} onClose={() => updateHelpOpen(false)} /> : null}
375
+ </ShortcutContext.Provider>
376
+ );
377
+ }
378
+
379
+ function ShortcutHelpDialog({
380
+ entries,
381
+ onClose,
382
+ }: {
383
+ entries: ShortcutHelpEntry[];
384
+ onClose: () => void;
385
+ }) {
386
+ const closeRef = useRef<HTMLButtonElement>(null);
387
+ const previousFocusRef = useRef<HTMLElement | null>(
388
+ typeof document !== 'undefined' && document.activeElement instanceof HTMLElement
389
+ ? document.activeElement
390
+ : null,
391
+ );
392
+ useEffect(() => {
393
+ closeRef.current?.focus({ preventScroll: true });
394
+ return () => previousFocusRef.current?.focus({ preventScroll: true });
395
+ }, []);
396
+ const grouped = HELP_SCOPE_ORDER.map((scope) => ({
397
+ scope,
398
+ entries: entries.filter((entry) => entry.scope === scope),
399
+ })).filter((group) => group.entries.length > 0);
400
+
401
+ return (
402
+ <div
403
+ role="presentation"
404
+ data-notis-shortcut-help-backdrop=""
405
+ onMouseDown={(event) => {
406
+ if (event.target === event.currentTarget) onClose();
407
+ }}
408
+ style={helpBackdropStyle}
409
+ >
410
+ <section
411
+ role="dialog"
412
+ aria-modal="true"
413
+ aria-labelledby="notis-shortcut-help-title"
414
+ data-notis-shortcut-help=""
415
+ onKeyDown={(event) => {
416
+ // The help surface has one interactive control. Keep keyboard focus
417
+ // in the modal instead of allowing Tab to escape into app chrome.
418
+ if (event.key === 'Tab') {
419
+ event.preventDefault();
420
+ closeRef.current?.focus({ preventScroll: true });
421
+ }
422
+ }}
423
+ style={helpDialogStyle}
424
+ >
425
+ <header style={helpHeaderStyle}>
426
+ <div>
427
+ <h2 id="notis-shortcut-help-title" style={helpTitleStyle}>Keyboard shortcuts</h2>
428
+ <p style={helpSubtitleStyle}>Available in this view</p>
429
+ </div>
430
+ <button
431
+ ref={closeRef}
432
+ type="button"
433
+ aria-label="Close keyboard shortcuts"
434
+ onClick={onClose}
435
+ style={helpCloseStyle}
436
+ >
437
+ Esc
438
+ </button>
439
+ </header>
440
+ <div style={helpBodyStyle}>
441
+ {grouped.map((group) => (
442
+ <section key={group.scope} aria-label={HELP_SCOPE_LABELS[group.scope]}>
443
+ <h3 style={helpGroupTitleStyle}>{HELP_SCOPE_LABELS[group.scope]}</h3>
444
+ <div style={helpListStyle}>
445
+ {group.entries.map((entry) => (
446
+ <div key={`${group.scope}:${entry.id}`} style={helpRowStyle}>
447
+ <span>{entry.label}</span>
448
+ <span style={helpKeysStyle}>
449
+ {entry.keys.map((keys) => (
450
+ <kbd key={keys} style={helpKeyStyle}>{shortcutDisplay(keys)}</kbd>
451
+ ))}
452
+ </span>
453
+ </div>
454
+ ))}
455
+ </div>
456
+ </section>
457
+ ))}
458
+ <section aria-label="Help">
459
+ <h3 style={helpGroupTitleStyle}>Help</h3>
460
+ <div style={helpListStyle}>
461
+ <div style={helpRowStyle}>
462
+ <span>Show keyboard shortcuts</span>
463
+ <kbd style={helpKeyStyle}>?</kbd>
464
+ </div>
465
+ </div>
466
+ </section>
467
+ </div>
468
+ </section>
469
+ </div>
470
+ );
471
+ }
472
+
473
+ const helpBackdropStyle: React.CSSProperties = {
474
+ position: 'fixed',
475
+ inset: 0,
476
+ zIndex: 1000,
477
+ display: 'grid',
478
+ placeItems: 'center',
479
+ padding: '1.5rem',
480
+ background: 'rgba(0, 0, 0, 0.42)',
481
+ backdropFilter: 'blur(3px)',
482
+ };
483
+
484
+ const helpDialogStyle: React.CSSProperties = {
485
+ width: 'min(34rem, 100%)',
486
+ maxHeight: 'min(42rem, calc(100dvh - 3rem))',
487
+ overflow: 'hidden',
488
+ border: '1px solid hsl(var(--border))',
489
+ borderRadius: '0.875rem',
490
+ background: 'hsl(var(--background))',
491
+ color: 'hsl(var(--foreground))',
492
+ boxShadow: '0 24px 70px rgba(0, 0, 0, 0.28)',
493
+ fontFamily: 'inherit',
494
+ };
495
+
496
+ const helpHeaderStyle: React.CSSProperties = {
497
+ display: 'flex',
498
+ alignItems: 'flex-start',
499
+ justifyContent: 'space-between',
500
+ gap: '1rem',
501
+ padding: '1.25rem 1.25rem 1rem',
502
+ borderBottom: '1px solid hsl(var(--border))',
503
+ };
504
+
505
+ const helpTitleStyle: React.CSSProperties = { margin: 0, fontSize: '1rem', fontWeight: 650 };
506
+ const helpSubtitleStyle: React.CSSProperties = {
507
+ margin: '0.2rem 0 0',
508
+ color: 'hsl(var(--muted-foreground))',
509
+ fontSize: '0.75rem',
510
+ };
511
+ const helpCloseStyle: React.CSSProperties = {
512
+ border: '1px solid hsl(var(--border))',
513
+ borderRadius: '0.35rem',
514
+ background: 'hsl(var(--muted))',
515
+ color: 'hsl(var(--muted-foreground))',
516
+ padding: '0.2rem 0.45rem',
517
+ cursor: 'pointer',
518
+ font: 'inherit',
519
+ fontSize: '0.7rem',
520
+ };
521
+ const helpBodyStyle: React.CSSProperties = {
522
+ display: 'grid',
523
+ gap: '1.1rem',
524
+ maxHeight: 'calc(min(42rem, 100dvh - 3rem) - 5rem)',
525
+ overflowY: 'auto',
526
+ padding: '1rem 1.25rem 1.25rem',
527
+ };
528
+ const helpGroupTitleStyle: React.CSSProperties = {
529
+ margin: '0 0 0.35rem',
530
+ color: 'hsl(var(--muted-foreground))',
531
+ fontSize: '0.68rem',
532
+ fontWeight: 650,
533
+ letterSpacing: '0.06em',
534
+ textTransform: 'uppercase',
535
+ };
536
+ const helpListStyle: React.CSSProperties = { display: 'grid' };
537
+ const helpRowStyle: React.CSSProperties = {
538
+ display: 'flex',
539
+ minHeight: '2rem',
540
+ alignItems: 'center',
541
+ justifyContent: 'space-between',
542
+ gap: '1rem',
543
+ borderBottom: '1px solid color-mix(in srgb, hsl(var(--border)) 65%, transparent)',
544
+ fontSize: '0.8rem',
545
+ };
546
+ const helpKeysStyle: React.CSSProperties = {
547
+ display: 'inline-flex',
548
+ flexWrap: 'wrap',
549
+ justifyContent: 'flex-end',
550
+ gap: '0.3rem',
551
+ };
552
+ const helpKeyStyle: React.CSSProperties = {
553
+ minWidth: '1.6rem',
554
+ border: '1px solid hsl(var(--border))',
555
+ borderBottomWidth: '2px',
556
+ borderRadius: '0.35rem',
557
+ background: 'hsl(var(--muted))',
558
+ color: 'hsl(var(--foreground))',
559
+ padding: '0.15rem 0.4rem',
560
+ textAlign: 'center',
561
+ font: 'inherit',
562
+ fontSize: '0.72rem',
563
+ };
564
+
565
+ export function useShortcuts(
566
+ shortcuts: ShortcutDefinition[],
567
+ options: UseShortcutsOptions = {},
568
+ ): void {
569
+ const registry = useContext(ShortcutContext);
570
+ const shortcutsRef = useRef(shortcuts);
571
+ shortcutsRef.current = shortcuts;
572
+ const {
573
+ enabled = true,
574
+ scope = 'route',
575
+ priority = 0,
576
+ sequenceTimeoutMs = 1500,
577
+ collectionOwnerId,
578
+ } = options;
579
+
580
+ useEffect(() => {
581
+ if (!registry || !enabled) return;
582
+ return registry.register({
583
+ enabled,
584
+ scope,
585
+ priority: SHORTCUT_SCOPE_PRIORITY[scope] + priority,
586
+ sequenceTimeoutMs,
587
+ collectionOwnerId,
588
+ getShortcuts: () => shortcutsRef.current,
589
+ });
590
+ }, [collectionOwnerId, enabled, priority, registry, scope, sequenceTimeoutMs]);
591
+
592
+ // Compatibility fallback for SDK components rendered outside a provider.
593
+ useEffect(() => {
594
+ if (registry || !enabled) return;
595
+ const handleKeyDown = (event: KeyboardEvent) => {
596
+ if (isEditableShortcutEvent(event)) return;
597
+ const token = chordToken(eventChord(event));
598
+ for (const shortcut of shortcutsRef.current) {
599
+ if (shortcut.enabled === false || event.repeat && !shortcut.allowRepeat) continue;
600
+ const match = parseShortcut(shortcut).find(
601
+ (candidate) => candidate.sequence.length === 1 && chordToken(candidate.sequence[0]!) === token,
602
+ );
603
+ if (match) {
604
+ consumeShortcut(event, shortcut);
605
+ return;
606
+ }
607
+ }
608
+ };
609
+ document.addEventListener('keydown', handleKeyDown, true);
610
+ return () => document.removeEventListener('keydown', handleKeyDown, true);
611
+ }, [enabled, registry]);
612
+ }
613
+
614
+ export function shortcutDisplay(raw: string): string {
615
+ return raw
616
+ .trim()
617
+ .split(/\s+/)
618
+ .map((chord) => chord
619
+ .split('+')
620
+ .map((part) => {
621
+ const lower = part.toLowerCase();
622
+ if (lower === 'mod' || lower === 'cmd' || lower === 'meta') return '⌘';
623
+ if (lower === 'ctrl') return '⌃';
624
+ if (lower === 'shift') return '⇧';
625
+ if (lower === 'alt' || lower === 'option') return '⌥';
626
+ if (lower === 'arrowup' || lower === 'up') return '↑';
627
+ if (lower === 'arrowdown' || lower === 'down') return '↓';
628
+ if (lower === 'arrowleft' || lower === 'left') return '←';
629
+ if (lower === 'arrowright' || lower === 'right') return '→';
630
+ return part.length === 1 ? part.toUpperCase() : part;
631
+ })
632
+ .join(''))
633
+ .join(' ');
634
+ }
@@ -0,0 +1,41 @@
1
+ export {
2
+ ShortcutProvider,
3
+ SHORTCUT_SCOPE_PRIORITY,
4
+ isEditableShortcutEvent,
5
+ shortcutDisplay,
6
+ useShortcuts,
7
+ } from './interactions/shortcuts';
8
+ export type {
9
+ ShortcutDefinition,
10
+ ShortcutHelpEntry,
11
+ ShortcutScope,
12
+ UseShortcutsOptions,
13
+ } from './interactions/shortcuts';
14
+ export { collectionActionDefaults } from './interactions/actions';
15
+ export type {
16
+ CollectionAction,
17
+ CollectionActionContext,
18
+ CollectionActionIntent,
19
+ ResolvedCollectionAction,
20
+ } from './interactions/actions';
21
+ export { useCollectionInteractions, COLLECTION_ITEM_ATTRIBUTE } from './hooks/useCollectionInteractions';
22
+ export type {
23
+ CollectionInteractionController,
24
+ CollectionInteractionReason,
25
+ CollectionKeyboardShortcuts,
26
+ CollectionNavigationContext,
27
+ CollectionNavigationDirection,
28
+ CollectionSelectionChange,
29
+ SelectionMarqueeRect,
30
+ UseCollectionInteractionsOptions,
31
+ } from './hooks/useCollectionInteractions';
32
+ export { MultiSelectActionBar } from './components/MultiSelectActionBar';
33
+ export { MultiSelectCheckbox, MultiSelectCheckbox as SelectionCheckbox } from './components/MultiSelectCheckbox';
34
+ export { MultiSelectDragOverlay, MultiSelectDragOverlay as SelectionMarquee } from './components/MultiSelectDragOverlay';
35
+ export { ShortcutHints } from './components/ShortcutHints';
36
+ export type { MultiSelectAction, MultiSelectActionBarProps } from './components/MultiSelectActionBar';
37
+ export type { MultiSelectCheckboxProps } from './components/MultiSelectCheckbox';
38
+ export type { MultiSelectDragOverlayProps } from './components/MultiSelectDragOverlay';
39
+ export type { MultiSelectCheckboxProps as SelectionCheckboxProps } from './components/MultiSelectCheckbox';
40
+ export type { MultiSelectDragOverlayProps as SelectionMarqueeProps } from './components/MultiSelectDragOverlay';
41
+ export type { ShortcutHint, ShortcutHintsProps } from './components/ShortcutHints';
@@ -2,6 +2,7 @@
2
2
 
3
3
  import React, { createContext, useContext, type Context, type ReactNode } from 'react';
4
4
  import type { NotisRuntime } from './runtime';
5
+ import { ShortcutProvider } from './interactions/shortcuts';
5
6
 
6
7
  const NOTIS_CONTEXT_SYMBOL = Symbol.for('notis.sdk.runtime_context');
7
8
 
@@ -29,7 +30,7 @@ export function NotisProvider({ children, runtime }: { children: ReactNode; runt
29
30
  }
30
31
  return (
31
32
  <NotisContext.Provider value={runtime}>
32
- {children}
33
+ <ShortcutProvider>{children}</ShortcutProvider>
33
34
  </NotisContext.Provider>
34
35
  );
35
36
  }