@notis_ai/cli 0.2.14 → 0.2.15

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 (296) hide show
  1. package/README.md +82 -20
  2. package/dist/agent-hooks/notis-agent-hook.mjs +19750 -0
  3. package/{skills → dist/base-skills}/notis-apps/SKILL.md +84 -53
  4. package/{skills → dist/base-skills}/notis-cli/SKILL.md +9 -9
  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 +38 -17
  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 +871 -387
  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-roots.js +284 -0
  25. package/src/runtime/app-dev-server.js +212 -22
  26. package/src/runtime/app-dev-sessions.js +105 -145
  27. package/src/runtime/app-platform.js +1291 -165
  28. package/src/runtime/app-registry-scaffolds.js +367 -0
  29. package/src/runtime/base-skills.d.ts +20 -0
  30. package/src/runtime/base-skills.js +167 -0
  31. package/src/runtime/login-listener.js +15 -0
  32. package/src/runtime/oauth.js +1506 -141
  33. package/src/runtime/output.js +7 -0
  34. package/src/runtime/ports.js +16 -0
  35. package/src/runtime/profiles.js +0 -5
  36. package/src/runtime/skill-sync/cloud-client.ts +96 -0
  37. package/src/runtime/skill-sync/index.ts +644 -0
  38. package/src/runtime/skill-sync/local-scanner.ts +1046 -0
  39. package/src/runtime/skill-sync/symlink-manager.ts +383 -0
  40. package/src/runtime/skill-sync/sync-plan.ts +22 -0
  41. package/src/runtime/skill-sync/types.ts +103 -0
  42. package/src/runtime/skill-sync/write-cloud-skill.ts +50 -0
  43. package/src/runtime/store-screenshot.js +6 -1
  44. package/src/runtime/sync-skills.d.ts +37 -0
  45. package/src/runtime/sync-skills.js +215 -0
  46. package/template/app/layout.tsx +5 -1
  47. package/template/notis.config.ts +1 -0
  48. package/template/packages/sdk/package.json +2 -1
  49. package/template/packages/sdk/src/components/DocumentEditor.tsx +13 -3
  50. package/template/packages/sdk/src/components/MarkdownEditor.tsx +121 -0
  51. package/template/packages/sdk/src/components/MultiSelectActionBar.tsx +37 -119
  52. package/template/packages/sdk/src/components/MultiSelectCheckbox.tsx +5 -1
  53. package/template/packages/sdk/src/components/MultiSelectDragOverlay.tsx +1 -1
  54. package/template/packages/sdk/src/components/NotisSelectionBoundary.tsx +55 -0
  55. package/template/packages/sdk/src/components/ShortcutHints.tsx +56 -0
  56. package/template/packages/sdk/src/config.ts +19 -0
  57. package/template/packages/sdk/src/documents.ts +7 -1
  58. package/template/packages/sdk/src/hooks/useActiveResource.ts +19 -0
  59. package/template/packages/sdk/src/hooks/useCollectionInteractions.ts +709 -0
  60. package/template/packages/sdk/src/hooks/useHandover.ts +3 -0
  61. package/template/packages/sdk/src/hooks/useMultiSelect.ts +44 -482
  62. package/template/packages/sdk/src/hooks/useTool.ts +5 -4
  63. package/template/packages/sdk/src/index.ts +47 -2
  64. package/template/packages/sdk/src/interactions/actions.ts +46 -0
  65. package/template/packages/sdk/src/interactions/shortcuts.tsx +628 -0
  66. package/template/packages/sdk/src/interactions.ts +41 -0
  67. package/template/packages/sdk/src/provider.tsx +2 -1
  68. package/template/packages/sdk/src/runtime.ts +89 -4
  69. package/dist/scaffolds/notis-database/CHANGELOG.md +0 -5
  70. package/dist/scaffolds/notis-database/app/globals.css +0 -44
  71. package/dist/scaffolds/notis-database/app/layout.tsx +0 -6
  72. package/dist/scaffolds/notis-database/app/page.tsx +0 -1088
  73. package/dist/scaffolds/notis-database/components/ui/badge.tsx +0 -28
  74. package/dist/scaffolds/notis-database/components/ui/button.tsx +0 -53
  75. package/dist/scaffolds/notis-database/components/ui/card.tsx +0 -56
  76. package/dist/scaffolds/notis-database/components/ui/table.tsx +0 -120
  77. package/dist/scaffolds/notis-database/components.json +0 -20
  78. package/dist/scaffolds/notis-database/index.html +0 -12
  79. package/dist/scaffolds/notis-database/lib/types.ts +0 -132
  80. package/dist/scaffolds/notis-database/lib/utils.ts +0 -6
  81. package/dist/scaffolds/notis-database/metadata/screenshot-1.png +0 -0
  82. package/dist/scaffolds/notis-database/metadata/screenshot-2.png +0 -0
  83. package/dist/scaffolds/notis-database/metadata/screenshot-3.png +0 -0
  84. package/dist/scaffolds/notis-database/metadata/screenshot-4.png +0 -0
  85. package/dist/scaffolds/notis-database/metadata/screenshot-5.png +0 -0
  86. package/dist/scaffolds/notis-database/metadata/screenshot-fixtures.json +0 -1839
  87. package/dist/scaffolds/notis-database/notis.config.ts +0 -73
  88. package/dist/scaffolds/notis-database/package-lock.json +0 -3935
  89. package/dist/scaffolds/notis-database/package.json +0 -32
  90. package/dist/scaffolds/notis-database/packages/sdk/package.json +0 -36
  91. package/dist/scaffolds/notis-database/packages/sdk/src/components/DocumentEditor.tsx +0 -93
  92. package/dist/scaffolds/notis-database/packages/sdk/src/components/Markdown.tsx +0 -60
  93. package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
  94. package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
  95. package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
  96. package/dist/scaffolds/notis-database/packages/sdk/src/config.ts +0 -234
  97. package/dist/scaffolds/notis-database/packages/sdk/src/documents.ts +0 -250
  98. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useBackend.ts +0 -41
  99. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
  100. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
  101. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
  102. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocument.ts +0 -78
  103. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocuments.ts +0 -121
  104. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useHandover.ts +0 -75
  105. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
  106. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useNotis.ts +0 -34
  107. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
  108. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTool.ts +0 -64
  109. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTools.ts +0 -56
  110. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
  111. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
  112. package/dist/scaffolds/notis-database/packages/sdk/src/index.ts +0 -100
  113. package/dist/scaffolds/notis-database/packages/sdk/src/provider.tsx +0 -43
  114. package/dist/scaffolds/notis-database/packages/sdk/src/runtime.ts +0 -351
  115. package/dist/scaffolds/notis-database/packages/sdk/src/styles.css +0 -186
  116. package/dist/scaffolds/notis-database/packages/sdk/src/ui.ts +0 -15
  117. package/dist/scaffolds/notis-database/packages/sdk/src/vite.ts +0 -56
  118. package/dist/scaffolds/notis-database/packages/sdk/tsconfig.json +0 -15
  119. package/dist/scaffolds/notis-database/postcss.config.mjs +0 -8
  120. package/dist/scaffolds/notis-database/src/dev-main.tsx +0 -23
  121. package/dist/scaffolds/notis-database/src/mock-runtime.ts +0 -561
  122. package/dist/scaffolds/notis-database/tailwind.config.ts +0 -59
  123. package/dist/scaffolds/notis-database/tsconfig.json +0 -23
  124. package/dist/scaffolds/notis-database/vite.config.ts +0 -22
  125. package/dist/scaffolds/notis-journal/CHANGELOG.md +0 -29
  126. package/dist/scaffolds/notis-journal/app/globals.css +0 -37
  127. package/dist/scaffolds/notis-journal/app/insights/page.tsx +0 -513
  128. package/dist/scaffolds/notis-journal/app/journal-core.tsx +0 -362
  129. package/dist/scaffolds/notis-journal/app/journal-ui.tsx +0 -337
  130. package/dist/scaffolds/notis-journal/app/layout.tsx +0 -6
  131. package/dist/scaffolds/notis-journal/app/page.tsx +0 -486
  132. package/dist/scaffolds/notis-journal/components/ui/badge.tsx +0 -28
  133. package/dist/scaffolds/notis-journal/components/ui/button.tsx +0 -53
  134. package/dist/scaffolds/notis-journal/components/ui/card.tsx +0 -56
  135. package/dist/scaffolds/notis-journal/components.json +0 -20
  136. package/dist/scaffolds/notis-journal/index.html +0 -12
  137. package/dist/scaffolds/notis-journal/lib/utils.ts +0 -6
  138. package/dist/scaffolds/notis-journal/metadata/screenshot-1.png +0 -0
  139. package/dist/scaffolds/notis-journal/metadata/screenshot-2.png +0 -0
  140. package/dist/scaffolds/notis-journal/metadata/screenshot-3.png +0 -0
  141. package/dist/scaffolds/notis-journal/metadata/screenshot-4.png +0 -0
  142. package/dist/scaffolds/notis-journal/metadata/screenshot-5.png +0 -0
  143. package/dist/scaffolds/notis-journal/metadata/screenshot-6.png +0 -0
  144. package/dist/scaffolds/notis-journal/metadata/screenshot-fixtures.json +0 -132
  145. package/dist/scaffolds/notis-journal/notis.config.ts +0 -93
  146. package/dist/scaffolds/notis-journal/package-lock.json +0 -4615
  147. package/dist/scaffolds/notis-journal/package.json +0 -34
  148. package/dist/scaffolds/notis-journal/packages/sdk/package.json +0 -36
  149. package/dist/scaffolds/notis-journal/packages/sdk/src/components/DocumentEditor.tsx +0 -93
  150. package/dist/scaffolds/notis-journal/packages/sdk/src/components/Markdown.tsx +0 -60
  151. package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
  152. package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
  153. package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
  154. package/dist/scaffolds/notis-journal/packages/sdk/src/config.ts +0 -234
  155. package/dist/scaffolds/notis-journal/packages/sdk/src/documents.ts +0 -250
  156. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useBackend.ts +0 -41
  157. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
  158. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
  159. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
  160. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDocument.ts +0 -78
  161. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDocuments.ts +0 -121
  162. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useHandover.ts +0 -75
  163. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
  164. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useNotis.ts +0 -34
  165. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
  166. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTool.ts +0 -64
  167. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTools.ts +0 -56
  168. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
  169. package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
  170. package/dist/scaffolds/notis-journal/packages/sdk/src/index.ts +0 -100
  171. package/dist/scaffolds/notis-journal/packages/sdk/src/provider.tsx +0 -43
  172. package/dist/scaffolds/notis-journal/packages/sdk/src/runtime.ts +0 -351
  173. package/dist/scaffolds/notis-journal/packages/sdk/src/styles.css +0 -186
  174. package/dist/scaffolds/notis-journal/packages/sdk/src/ui.ts +0 -15
  175. package/dist/scaffolds/notis-journal/packages/sdk/src/vite.ts +0 -56
  176. package/dist/scaffolds/notis-journal/packages/sdk/tsconfig.json +0 -15
  177. package/dist/scaffolds/notis-journal/postcss.config.mjs +0 -8
  178. package/dist/scaffolds/notis-journal/skills/journal-onboarding/SKILL.md +0 -120
  179. package/dist/scaffolds/notis-journal/src/dev-main.tsx +0 -58
  180. package/dist/scaffolds/notis-journal/src/mock-runtime.ts +0 -199
  181. package/dist/scaffolds/notis-journal/tailwind.config.ts +0 -58
  182. package/dist/scaffolds/notis-journal/tsconfig.json +0 -23
  183. package/dist/scaffolds/notis-journal/vite.config.ts +0 -10
  184. package/dist/scaffolds/notis-notes/CHANGELOG.md +0 -5
  185. package/dist/scaffolds/notis-notes/app/globals.css +0 -3
  186. package/dist/scaffolds/notis-notes/app/layout.tsx +0 -6
  187. package/dist/scaffolds/notis-notes/app/page.tsx +0 -1943
  188. package/dist/scaffolds/notis-notes/app/phosphor-icons.ts +0 -596
  189. package/dist/scaffolds/notis-notes/components/ui/badge.tsx +0 -28
  190. package/dist/scaffolds/notis-notes/components/ui/button.tsx +0 -53
  191. package/dist/scaffolds/notis-notes/components/ui/card.tsx +0 -56
  192. package/dist/scaffolds/notis-notes/components.json +0 -20
  193. package/dist/scaffolds/notis-notes/lib/utils.ts +0 -6
  194. package/dist/scaffolds/notis-notes/lib/visible-properties.ts +0 -144
  195. package/dist/scaffolds/notis-notes/metadata/screenshot-1.png +0 -0
  196. package/dist/scaffolds/notis-notes/metadata/screenshot-2.png +0 -0
  197. package/dist/scaffolds/notis-notes/metadata/screenshot-3.png +0 -0
  198. package/dist/scaffolds/notis-notes/metadata/screenshot-4.png +0 -0
  199. package/dist/scaffolds/notis-notes/metadata/screenshot-5.png +0 -0
  200. package/dist/scaffolds/notis-notes/metadata/screenshot-fixtures.json +0 -752
  201. package/dist/scaffolds/notis-notes/notis.config.ts +0 -80
  202. package/dist/scaffolds/notis-notes/package-lock.json +0 -4636
  203. package/dist/scaffolds/notis-notes/package.json +0 -35
  204. package/dist/scaffolds/notis-notes/packages/sdk/package.json +0 -36
  205. package/dist/scaffolds/notis-notes/packages/sdk/src/components/DocumentEditor.tsx +0 -93
  206. package/dist/scaffolds/notis-notes/packages/sdk/src/components/Markdown.tsx +0 -60
  207. package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
  208. package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
  209. package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
  210. package/dist/scaffolds/notis-notes/packages/sdk/src/config.ts +0 -234
  211. package/dist/scaffolds/notis-notes/packages/sdk/src/documents.ts +0 -250
  212. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useBackend.ts +0 -41
  213. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
  214. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
  215. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
  216. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocument.ts +0 -78
  217. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocuments.ts +0 -121
  218. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useHandover.ts +0 -75
  219. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
  220. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotis.ts +0 -34
  221. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
  222. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTool.ts +0 -64
  223. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTools.ts +0 -56
  224. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
  225. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
  226. package/dist/scaffolds/notis-notes/packages/sdk/src/index.ts +0 -100
  227. package/dist/scaffolds/notis-notes/packages/sdk/src/provider.tsx +0 -43
  228. package/dist/scaffolds/notis-notes/packages/sdk/src/runtime.ts +0 -351
  229. package/dist/scaffolds/notis-notes/packages/sdk/src/styles.css +0 -186
  230. package/dist/scaffolds/notis-notes/packages/sdk/src/ui.ts +0 -15
  231. package/dist/scaffolds/notis-notes/packages/sdk/src/vite.ts +0 -56
  232. package/dist/scaffolds/notis-notes/packages/sdk/tsconfig.json +0 -15
  233. package/dist/scaffolds/notis-notes/postcss.config.mjs +0 -8
  234. package/dist/scaffolds/notis-notes/tailwind.config.ts +0 -58
  235. package/dist/scaffolds/notis-notes/tsconfig.json +0 -23
  236. package/dist/scaffolds/notis-notes/vite.config.ts +0 -10
  237. package/dist/scaffolds/notis-random/CHANGELOG.md +0 -15
  238. package/dist/scaffolds/notis-random/README.md +0 -33
  239. package/dist/scaffolds/notis-random/app/globals.css +0 -11
  240. package/dist/scaffolds/notis-random/app/history/page.tsx +0 -67
  241. package/dist/scaffolds/notis-random/app/layout.tsx +0 -7
  242. package/dist/scaffolds/notis-random/app/page.tsx +0 -289
  243. package/dist/scaffolds/notis-random/components/ui/button.tsx +0 -50
  244. package/dist/scaffolds/notis-random/components/ui/card.tsx +0 -16
  245. package/dist/scaffolds/notis-random/components/ui/input.tsx +0 -23
  246. package/dist/scaffolds/notis-random/components.json +0 -20
  247. package/dist/scaffolds/notis-random/index.html +0 -12
  248. package/dist/scaffolds/notis-random/lib/notis-tools.ts +0 -128
  249. package/dist/scaffolds/notis-random/lib/rng.ts +0 -202
  250. package/dist/scaffolds/notis-random/lib/roll-record.ts +0 -189
  251. package/dist/scaffolds/notis-random/lib/utils.ts +0 -25
  252. package/dist/scaffolds/notis-random/metadata/screenshot-1.png +0 -0
  253. package/dist/scaffolds/notis-random/metadata/screenshot-2.png +0 -0
  254. package/dist/scaffolds/notis-random/metadata/screenshot-3.png +0 -0
  255. package/dist/scaffolds/notis-random/metadata/screenshot-4.png +0 -0
  256. package/dist/scaffolds/notis-random/metadata/screenshot-5.png +0 -0
  257. package/dist/scaffolds/notis-random/metadata/screenshot-fixtures.json +0 -753
  258. package/dist/scaffolds/notis-random/notis.config.ts +0 -86
  259. package/dist/scaffolds/notis-random/package-lock.json +0 -4513
  260. package/dist/scaffolds/notis-random/package.json +0 -36
  261. package/dist/scaffolds/notis-random/packages/sdk/package.json +0 -36
  262. package/dist/scaffolds/notis-random/packages/sdk/src/components/DocumentEditor.tsx +0 -93
  263. package/dist/scaffolds/notis-random/packages/sdk/src/components/Markdown.tsx +0 -60
  264. package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
  265. package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
  266. package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
  267. package/dist/scaffolds/notis-random/packages/sdk/src/config.ts +0 -234
  268. package/dist/scaffolds/notis-random/packages/sdk/src/documents.ts +0 -250
  269. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useBackend.ts +0 -41
  270. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
  271. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
  272. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
  273. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocument.ts +0 -78
  274. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocuments.ts +0 -121
  275. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useHandover.ts +0 -75
  276. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
  277. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotis.ts +0 -34
  278. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
  279. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTool.ts +0 -64
  280. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTools.ts +0 -56
  281. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
  282. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
  283. package/dist/scaffolds/notis-random/packages/sdk/src/index.ts +0 -100
  284. package/dist/scaffolds/notis-random/packages/sdk/src/provider.tsx +0 -43
  285. package/dist/scaffolds/notis-random/packages/sdk/src/runtime.ts +0 -351
  286. package/dist/scaffolds/notis-random/packages/sdk/src/styles.css +0 -186
  287. package/dist/scaffolds/notis-random/packages/sdk/src/ui.ts +0 -15
  288. package/dist/scaffolds/notis-random/packages/sdk/src/vite.ts +0 -56
  289. package/dist/scaffolds/notis-random/packages/sdk/tsconfig.json +0 -15
  290. package/dist/scaffolds/notis-random/postcss.config.mjs +0 -6
  291. package/dist/scaffolds/notis-random/src/dev-main.tsx +0 -70
  292. package/dist/scaffolds/notis-random/src/mock-runtime.ts +0 -129
  293. package/dist/scaffolds/notis-random/tailwind.config.ts +0 -50
  294. package/dist/scaffolds/notis-random/tsconfig.json +0 -23
  295. package/dist/scaffolds/notis-random/vite.config.ts +0 -11
  296. package/dist/scaffolds.json +0 -49
@@ -0,0 +1,284 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import {
3
+ existsSync,
4
+ lstatSync,
5
+ mkdirSync,
6
+ readFileSync,
7
+ readdirSync,
8
+ realpathSync,
9
+ renameSync,
10
+ rmSync,
11
+ statSync,
12
+ writeFileSync,
13
+ } from 'node:fs';
14
+ import { homedir } from 'node:os';
15
+ import { dirname, join, parse as parsePath, resolve } from 'node:path';
16
+
17
+ import { usageError } from './errors.js';
18
+ import { CONFIG_DIR } from './profiles.js';
19
+
20
+ export const APP_DEV_ROOTS_VERSION = 1;
21
+ export const DEFAULT_APP_DEV_ROOT = join(homedir(), '.notis', 'apps');
22
+ export const DEFAULT_APP_DEV_ROOTS_FILE = join(CONFIG_DIR, 'app-dev-roots.json');
23
+ const CONFIG_FILENAMES = ['notis.config.ts', 'notis.config.js', 'notis.config.mjs'];
24
+ const LOCK_TIMEOUT_MS = 5_000;
25
+ const lockWait = new Int32Array(new SharedArrayBuffer(4));
26
+
27
+ function rootsFile(filePath) {
28
+ if (filePath) return resolve(filePath);
29
+ const envPath = process.env.NOTIS_APP_DEV_ROOTS_FILE;
30
+ return typeof envPath === 'string' && envPath.trim()
31
+ ? resolve(envPath.trim())
32
+ : DEFAULT_APP_DEV_ROOTS_FILE;
33
+ }
34
+
35
+ function legacyProjectsFile(filePath) {
36
+ const sessionsFile = filePath
37
+ || join(CONFIG_DIR, 'app-dev-sessions.json');
38
+ const parsed = parsePath(resolve(sessionsFile));
39
+ return join(parsed.dir, `${parsed.name}-projects${parsed.ext || '.json'}`);
40
+ }
41
+
42
+ function readLockOwner(lockPath) {
43
+ try {
44
+ return readFileSync(join(lockPath, 'owner'), 'utf8').trim();
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
49
+
50
+ function withLock(filePath, callback) {
51
+ mkdirSync(dirname(filePath), { recursive: true, mode: 0o700 });
52
+ const lockPath = `${filePath}.lock`;
53
+ const owner = `${process.pid}.${randomUUID()}`;
54
+ const startedAt = Date.now();
55
+ while (true) {
56
+ try {
57
+ mkdirSync(lockPath, { mode: 0o700 });
58
+ writeFileSync(join(lockPath, 'owner'), owner, { mode: 0o600 });
59
+ break;
60
+ } catch (error) {
61
+ if (error?.code !== 'EEXIST') throw error;
62
+ try {
63
+ const stat = lstatSync(lockPath);
64
+ if (!stat.isDirectory() || stat.isSymbolicLink()) {
65
+ throw new Error(`Refusing unsafe app development roots lock: ${lockPath}`);
66
+ }
67
+ const existingOwner = readLockOwner(lockPath);
68
+ const pid = Number.parseInt(String(existingOwner || '').split('.')[0] || '', 10);
69
+ if (Number.isInteger(pid) && pid > 0) {
70
+ try {
71
+ process.kill(pid, 0);
72
+ } catch (ownerError) {
73
+ if (ownerError?.code === 'ESRCH') {
74
+ rmSync(lockPath, { recursive: true, force: true });
75
+ continue;
76
+ }
77
+ }
78
+ }
79
+ } catch (statError) {
80
+ if (statError?.code === 'ENOENT') continue;
81
+ throw statError;
82
+ }
83
+ if (Date.now() - startedAt >= LOCK_TIMEOUT_MS) {
84
+ throw new Error(`Timed out waiting for app development roots lock: ${filePath}`);
85
+ }
86
+ Atomics.wait(lockWait, 0, 0, 10);
87
+ }
88
+ }
89
+ try {
90
+ return callback();
91
+ } finally {
92
+ if (readLockOwner(lockPath) === owner) {
93
+ rmSync(lockPath, { recursive: true, force: true });
94
+ }
95
+ }
96
+ }
97
+
98
+ function canonicalExistingDirectory(inputPath) {
99
+ const absolute = resolve(String(inputPath || ''));
100
+ let stat;
101
+ try {
102
+ stat = statSync(absolute);
103
+ } catch {
104
+ throw usageError(`App development root does not exist: ${absolute}`);
105
+ }
106
+ if (!stat.isDirectory()) {
107
+ throw usageError(`App development root is not a directory: ${absolute}`);
108
+ }
109
+ return realpathSync(absolute);
110
+ }
111
+
112
+ function normalizeRegistry(raw) {
113
+ const roots = Array.isArray(raw?.roots) ? raw.roots : [];
114
+ const seen = new Set();
115
+ return roots
116
+ .map((entry) => typeof entry === 'string' ? { path: entry } : entry)
117
+ .filter((entry) => entry && typeof entry.path === 'string' && entry.path.trim())
118
+ .map((entry) => ({
119
+ path: resolve(entry.path),
120
+ registeredAt: typeof entry.registeredAt === 'string'
121
+ ? entry.registeredAt
122
+ : new Date(0).toISOString(),
123
+ }))
124
+ .filter((entry) => {
125
+ if (seen.has(entry.path)) return false;
126
+ seen.add(entry.path);
127
+ return true;
128
+ });
129
+ }
130
+
131
+ function readRaw(filePath) {
132
+ if (!existsSync(filePath)) return { version: APP_DEV_ROOTS_VERSION, roots: [] };
133
+ try {
134
+ return {
135
+ version: APP_DEV_ROOTS_VERSION,
136
+ roots: normalizeRegistry(JSON.parse(readFileSync(filePath, 'utf8'))),
137
+ };
138
+ } catch {
139
+ return { version: APP_DEV_ROOTS_VERSION, roots: [] };
140
+ }
141
+ }
142
+
143
+ function writeRaw(registry, filePath) {
144
+ mkdirSync(dirname(filePath), { recursive: true, mode: 0o700 });
145
+ const normalized = {
146
+ version: APP_DEV_ROOTS_VERSION,
147
+ roots: normalizeRegistry(registry),
148
+ };
149
+ const temporary = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
150
+ writeFileSync(temporary, JSON.stringify(normalized, null, 2), { mode: 0o600 });
151
+ renameSync(temporary, filePath);
152
+ return normalized;
153
+ }
154
+
155
+ function migrateLegacyProjectsUnlocked(registry, options) {
156
+ const path = legacyProjectsFile(options.legacySessionsFilePath);
157
+ if (!existsSync(path)) return registry;
158
+ let projects = [];
159
+ try {
160
+ const raw = JSON.parse(readFileSync(path, 'utf8'));
161
+ projects = Array.isArray(raw?.projects) ? raw.projects : [];
162
+ } catch {
163
+ return registry;
164
+ }
165
+ const byPath = new Map(registry.roots.map((entry) => [entry.path, entry]));
166
+ for (const project of projects) {
167
+ if (typeof project?.projectDir !== 'string' || !project.projectDir.trim()) continue;
168
+ try {
169
+ const path = canonicalExistingDirectory(project.projectDir);
170
+ if (path === realpathIfExists(DEFAULT_APP_DEV_ROOT)) continue;
171
+ if (!byPath.has(path)) {
172
+ byPath.set(path, {
173
+ path,
174
+ registeredAt: typeof project.lastMountedAt === 'string'
175
+ ? project.lastMountedAt
176
+ : new Date().toISOString(),
177
+ });
178
+ }
179
+ } catch {
180
+ // Deleted or inaccessible legacy projects are intentionally not migrated.
181
+ }
182
+ }
183
+ rmSync(path, { force: true });
184
+ return { version: APP_DEV_ROOTS_VERSION, roots: [...byPath.values()] };
185
+ }
186
+
187
+ function realpathIfExists(path) {
188
+ try {
189
+ return realpathSync(path);
190
+ } catch {
191
+ return resolve(path);
192
+ }
193
+ }
194
+
195
+ export function readAppDevRoots(options = {}) {
196
+ const filePath = rootsFile(options.filePath);
197
+ let registry = readRaw(filePath);
198
+ if (options.migrateLegacy !== false) {
199
+ registry = withLock(filePath, () => {
200
+ const before = readRaw(filePath);
201
+ const after = migrateLegacyProjectsUnlocked(before, options);
202
+ const changed = JSON.stringify(before.roots) !== JSON.stringify(after.roots);
203
+ return changed ? writeRaw(after, filePath) : before;
204
+ });
205
+ }
206
+ const implicit = realpathIfExists(options.defaultRoot || DEFAULT_APP_DEV_ROOT);
207
+ const roots = [
208
+ { path: implicit, registeredAt: null, implicit: true },
209
+ ...registry.roots
210
+ .filter((entry) => entry.path !== implicit)
211
+ .map((entry) => ({ ...entry, implicit: false })),
212
+ ];
213
+ return { version: APP_DEV_ROOTS_VERSION, roots };
214
+ }
215
+
216
+ export function registerAppDevRoot(inputPath, options = {}) {
217
+ const path = canonicalExistingDirectory(inputPath);
218
+ const implicit = realpathIfExists(options.defaultRoot || DEFAULT_APP_DEV_ROOT);
219
+ if (path === implicit) return readAppDevRoots(options);
220
+ const filePath = rootsFile(options.filePath);
221
+ withLock(filePath, () => {
222
+ const registry = readRaw(filePath);
223
+ if (!registry.roots.some((entry) => entry.path === path)) {
224
+ registry.roots.push({ path, registeredAt: new Date().toISOString() });
225
+ writeRaw(registry, filePath);
226
+ }
227
+ });
228
+ return readAppDevRoots({ ...options, migrateLegacy: false });
229
+ }
230
+
231
+ export function removeAppDevRoot(inputPath, options = {}) {
232
+ const path = realpathIfExists(resolve(String(inputPath || '')));
233
+ const implicit = realpathIfExists(options.defaultRoot || DEFAULT_APP_DEV_ROOT);
234
+ if (path === implicit) {
235
+ throw usageError(`The default app development root cannot be removed: ${implicit}`);
236
+ }
237
+ const filePath = rootsFile(options.filePath);
238
+ let removed = false;
239
+ withLock(filePath, () => {
240
+ const registry = readRaw(filePath);
241
+ const next = registry.roots.filter((entry) => entry.path !== path);
242
+ removed = next.length !== registry.roots.length;
243
+ if (removed) writeRaw({ ...registry, roots: next }, filePath);
244
+ });
245
+ return { removed, path, registry: readAppDevRoots({ ...options, migrateLegacy: false }) };
246
+ }
247
+
248
+ function hasConfig(path) {
249
+ return CONFIG_FILENAMES.some((name) => existsSync(join(path, name)));
250
+ }
251
+
252
+ function childAppDirs(parent) {
253
+ if (!existsSync(parent)) return [];
254
+ let entries = [];
255
+ try {
256
+ entries = readdirSync(parent, { withFileTypes: true });
257
+ } catch {
258
+ return [];
259
+ }
260
+ return entries
261
+ .filter((entry) => entry.isDirectory())
262
+ .filter((entry) => !entry.name.startsWith('.') && !entry.name.startsWith('_'))
263
+ .filter((entry) => entry.name !== 'node_modules')
264
+ .map((entry) => join(parent, entry.name))
265
+ .filter(hasConfig);
266
+ }
267
+
268
+ export function discoverAppProjectsInRoot(inputRoot) {
269
+ const root = realpathIfExists(resolve(inputRoot));
270
+ const candidates = [];
271
+ if (hasConfig(root)) candidates.push(root);
272
+ candidates.push(...childAppDirs(root));
273
+ candidates.push(...childAppDirs(join(root, 'apps')));
274
+ return [...new Set(candidates.map(realpathIfExists))].sort();
275
+ }
276
+
277
+ export function discoverRegisteredAppProjects(options = {}) {
278
+ const roots = readAppDevRoots(options).roots;
279
+ return [...new Set(roots.flatMap((entry) => discoverAppProjectsInRoot(entry.path)))].sort();
280
+ }
281
+
282
+ export function getAppDevRootsFile(filePath) {
283
+ return rootsFile(filePath);
284
+ }
@@ -28,7 +28,10 @@ import {
28
28
  readLinkedState,
29
29
  writeLinkedState,
30
30
  } from './app-platform.js';
31
- import { linkAppDevSessionTarget } from './app-dev-sessions.js';
31
+ import {
32
+ linkAppDevSessionTarget,
33
+ readAppDevSessions,
34
+ } from './app-dev-sessions.js';
32
35
 
33
36
  const CONTENT_TYPES = {
34
37
  '.js': 'application/javascript; charset=utf-8',
@@ -297,7 +300,7 @@ function renderHarnessHtml({ state, manifest, appConfig, route, harnessOptions,
297
300
  /**
298
301
  * Start the dev server for one or more apps.
299
302
  *
300
- * @param {{apps: Array<{slug: string, projectDir: string, appId?: string, targetAppId?: string, userId?: string}>, port: number, watch?: boolean, sessionsFilePath?: string, harness?: { mode?: string, apiBase?: string, jwt?: string }, log?: (m: string) => void, logError?: (m: string) => void}} options
303
+ * @param {{apps: Array<{slug: string, projectDir: string, appId?: string, targetAppId?: string, userId?: string, profileKey?: string, sessionId?: string, mountNonce?: string}>, port: number, watch?: boolean, sessionsFilePath?: string, harness?: { mode?: string, apiBase?: string, jwt?: string }, log?: (m: string) => void, logError?: (m: string) => void}} options
301
304
  */
302
305
  export async function startAppDevServer({
303
306
  apps,
@@ -313,22 +316,43 @@ export async function startAppDevServer({
313
316
  }
314
317
 
315
318
  const appState = new Map();
316
- for (const app of apps) {
317
- appState.set(app.slug, {
319
+ // Sidebar reconciliation needs one stream for the shared host, not one
320
+ // long-lived HTTP/1.1 connection per discovered app. Keeping a stream per
321
+ // app exhausts Chromium's per-origin connection pool and can indefinitely
322
+ // queue the active view's bundle fetch during hot reload.
323
+ const hostSseClients = new Set();
324
+ const createAppState = (app) => {
325
+ let resolveBundleReady;
326
+ const bundleReadyPromise = new Promise((resolvePromise) => {
327
+ resolveBundleReady = resolvePromise;
328
+ });
329
+ return {
318
330
  slug: app.slug,
319
331
  projectDir: app.projectDir,
320
332
  appId: app.appId || null,
321
333
  targetAppId: app.targetAppId || null,
322
334
  userId: app.userId || null,
335
+ profileKey: app.profileKey || null,
336
+ sessionId: app.sessionId || null,
337
+ mountNonce: app.mountNonce || null,
323
338
  canonicalBundleDir: getBundleDir(app.projectDir),
324
339
  bundleDir: null,
325
340
  jsPath: null,
326
341
  sseClients: new Set(),
327
342
  watcher: null,
343
+ sourceWatchers: [],
344
+ prepareTimer: null,
345
+ reloadTimer: null,
328
346
  buildProcess: null,
329
347
  lastMtimeMs: 0,
330
348
  watchPollTimer: null,
331
- });
349
+ bundleReady: false,
350
+ bundleReadyPromise,
351
+ resolveBundleReady,
352
+ };
353
+ };
354
+ for (const app of apps) {
355
+ appState.set(app.slug, createAppState(app));
332
356
  }
333
357
 
334
358
  function broadcastReload(slug) {
@@ -342,6 +366,14 @@ export async function startAppDevServer({
342
366
  state.sseClients.delete(res);
343
367
  }
344
368
  }
369
+ const hostPayload = `event: reload\ndata: ${JSON.stringify({ slug, at: Date.now() })}\n\n`;
370
+ for (const res of hostSseClients) {
371
+ try {
372
+ res.write(hostPayload);
373
+ } catch {
374
+ hostSseClients.delete(res);
375
+ }
376
+ }
345
377
  }
346
378
 
347
379
  function matchAppRoute(pathname) {
@@ -374,6 +406,10 @@ export async function startAppDevServer({
374
406
  state.jsPath = join(bundleDir, 'app.js');
375
407
  try {
376
408
  state.lastMtimeMs = statSync(state.jsPath).mtimeMs;
409
+ if (!state.bundleReady) {
410
+ state.bundleReady = true;
411
+ state.resolveBundleReady();
412
+ }
377
413
  } catch {
378
414
  state.lastMtimeMs = 0;
379
415
  }
@@ -451,6 +487,9 @@ export async function startAppDevServer({
451
487
  try {
452
488
  const body = await readRequestJson(req);
453
489
  const appId = typeof body.app_id === 'string' ? body.app_id.trim() : '';
490
+ const version = Number.isInteger(body.version) && body.version > 0
491
+ ? body.version
492
+ : null;
454
493
  if (!appId) {
455
494
  res.writeHead(400, {
456
495
  ...headers,
@@ -459,20 +498,69 @@ export async function startAppDevServer({
459
498
  res.end(JSON.stringify({ error: 'app_id is required' }));
460
499
  return;
461
500
  }
501
+ if (version === null) {
502
+ res.writeHead(400, {
503
+ ...headers,
504
+ 'Content-Type': 'application/json; charset=utf-8',
505
+ });
506
+ res.end(JSON.stringify({ error: 'version must be a positive integer' }));
507
+ return;
508
+ }
509
+ const proofSession = readAppDevSessions(sessionsFilePath).sessions.find((session) => (
510
+ typeof body.session_id === 'string'
511
+ && typeof body.mount_nonce === 'string'
512
+ && session.sessionId === body.session_id
513
+ && session.mountNonce === body.mount_nonce
514
+ && session.devSlug === state.slug
515
+ && session.projectDir === state.projectDir
516
+ ));
517
+ if (!proofSession) {
518
+ res.writeHead(403, {
519
+ ...headers,
520
+ 'Content-Type': 'application/json; charset=utf-8',
521
+ });
522
+ res.end(JSON.stringify({ error: 'invalid app development session proof' }));
523
+ return;
524
+ }
525
+ const expectedAppId = proofSession.targetAppId || proofSession.appId;
526
+ if (!expectedAppId || appId !== expectedAppId) {
527
+ res.writeHead(409, {
528
+ ...headers,
529
+ 'Content-Type': 'application/json; charset=utf-8',
530
+ });
531
+ res.end(JSON.stringify({ error: 'app_id does not match this development session' }));
532
+ return;
533
+ }
462
534
 
463
- const currentState = readLinkedState(state.projectDir) || {};
535
+ const currentState = readLinkedState(
536
+ state.projectDir,
537
+ proofSession.profileKey || state.profileKey,
538
+ ) || {};
464
539
  const now = new Date().toISOString();
465
- writeLinkedState(state.projectDir, {
540
+ const promotedInPlace = proofSession.appId === appId;
541
+ const nextState = {
466
542
  ...currentState,
467
543
  app_id: appId,
468
- dev_app_id: state.appId || currentState.dev_app_id,
469
544
  linked_at: currentState.linked_at || now,
470
- dev_linked_at: currentState.dev_linked_at || now,
545
+ version,
546
+ deployed_at: now,
471
547
  updated_at: now,
472
- });
473
- state.targetAppId = appId;
548
+ };
549
+ if (promotedInPlace) {
550
+ delete nextState.dev_app_id;
551
+ delete nextState.dev_linked_at;
552
+ } else {
553
+ nextState.dev_app_id = proofSession.appId || currentState.dev_app_id;
554
+ nextState.dev_linked_at = currentState.dev_linked_at || now;
555
+ }
556
+ writeLinkedState(
557
+ state.projectDir,
558
+ nextState,
559
+ proofSession.profileKey || state.profileKey,
560
+ );
474
561
  linkAppDevSessionTarget({
475
- appId: state.appId,
562
+ sessionId: proofSession.sessionId,
563
+ appId: proofSession.appId,
476
564
  devSlug: state.slug,
477
565
  targetAppId: appId,
478
566
  lastHeartbeatAt: now,
@@ -480,8 +568,9 @@ export async function startAppDevServer({
480
568
  const response = {
481
569
  ok: true,
482
570
  app_id: appId,
483
- dev_app_id: state.appId || null,
571
+ dev_app_id: promotedInPlace ? null : proofSession.appId || null,
484
572
  target_app_id: appId,
573
+ version,
485
574
  };
486
575
  res.writeHead(200, {
487
576
  ...headers,
@@ -525,6 +614,18 @@ export async function startAppDevServer({
525
614
  return;
526
615
  }
527
616
 
617
+ if (url.pathname === '/events') {
618
+ res.writeHead(200, {
619
+ ...headers,
620
+ 'Content-Type': 'text/event-stream; charset=utf-8',
621
+ Connection: 'keep-alive',
622
+ });
623
+ res.write(': connected\n\n');
624
+ hostSseClients.add(res);
625
+ req.on('close', () => hostSseClients.delete(res));
626
+ return;
627
+ }
628
+
528
629
  const routed = matchAppRoute(url.pathname);
529
630
  if (!routed) {
530
631
  res.writeHead(404, headers);
@@ -644,15 +745,23 @@ export async function startAppDevServer({
644
745
  try {
645
746
  state.watcher = fsWatch(state.bundleDir, { persistent: false }, (_event, filename) => {
646
747
  if (!filename || filename !== 'app.js') return;
647
- try {
648
- const stat = statSync(state.jsPath);
649
- if (stat.mtimeMs === state.lastMtimeMs) return;
650
- state.lastMtimeMs = stat.mtimeMs;
651
- log(`[notis apps dev] ${state.slug}: bundle updated — reloading portal`);
652
- broadcastReload(state.slug);
653
- } catch {
654
- // Bundle file may be missing mid-write; ignore.
655
- }
748
+ // Vite may emit the filesystem notification while its output file is
749
+ // still being replaced. Debounce the notification so consumers never
750
+ // fetch the previous or partially-written bundle under the new build
751
+ // revision.
752
+ if (state.reloadTimer) clearTimeout(state.reloadTimer);
753
+ state.reloadTimer = setTimeout(() => {
754
+ state.reloadTimer = null;
755
+ try {
756
+ const stat = statSync(state.jsPath);
757
+ if (stat.mtimeMs === state.lastMtimeMs) return;
758
+ state.lastMtimeMs = stat.mtimeMs;
759
+ log(`[notis apps dev] ${state.slug}: bundle updated — reloading portal`);
760
+ broadcastReload(state.slug);
761
+ } catch {
762
+ // Bundle file may be missing mid-write; the next change retries.
763
+ }
764
+ }, 100);
656
765
  });
657
766
  } catch (error) {
658
767
  logError(`[notis apps dev] ${state.slug}: watch failed: ${error.message}`);
@@ -669,9 +778,53 @@ export async function startAppDevServer({
669
778
  state.watchPollTimer = setTimeout(() => pollForBundleAndWatch(state), 300);
670
779
  }
671
780
 
781
+ function watchManifestInputs(state) {
782
+ const schedulePrepare = () => {
783
+ if (state.prepareTimer) clearTimeout(state.prepareTimer);
784
+ state.prepareTimer = setTimeout(() => {
785
+ state.prepareTimer = null;
786
+ void prepareArtifactBuild(state.projectDir).then(() => {
787
+ // Name, icon, and route metadata can change without changing the
788
+ // compiled JS bytes. Notify consumers immediately; a generated-entry
789
+ // change will produce the normal second reload after Vite rebuilds.
790
+ broadcastReload(state.slug);
791
+ }).catch((error) => {
792
+ const message = error instanceof Error ? error.message : String(error);
793
+ logError(`[notis apps dev] ${state.slug}: ${message}`);
794
+ });
795
+ }, 75);
796
+ };
797
+ try {
798
+ state.sourceWatchers.push(fsWatch(
799
+ state.projectDir,
800
+ { persistent: false },
801
+ (_event, filename) => {
802
+ if (filename && /^notis\.config\.(?:ts|js|mjs)$/.test(String(filename))) {
803
+ schedulePrepare();
804
+ }
805
+ },
806
+ ));
807
+ } catch (error) {
808
+ logError(`[notis apps dev] ${state.slug}: config watch failed: ${error.message}`);
809
+ }
810
+ const appDir = join(state.projectDir, 'app');
811
+ if (existsSync(appDir)) {
812
+ try {
813
+ state.sourceWatchers.push(fsWatch(
814
+ appDir,
815
+ { persistent: false, recursive: process.platform === 'darwin' || process.platform === 'win32' },
816
+ schedulePrepare,
817
+ ));
818
+ } catch (error) {
819
+ logError(`[notis apps dev] ${state.slug}: route watch failed: ${error.message}`);
820
+ }
821
+ }
822
+ }
823
+
672
824
  for (const state of appState.values()) {
673
825
  if (watch) {
674
826
  await prepareArtifactBuild(state.projectDir);
827
+ watchManifestInputs(state);
675
828
  pollForBundleAndWatch(state);
676
829
 
677
830
  state.buildProcess = spawn('npm', ['run', 'build', '--', '--watch'], {
@@ -694,8 +847,37 @@ export async function startAppDevServer({
694
847
 
695
848
  return {
696
849
  port,
850
+ updateApp(slug, updates = {}) {
851
+ const state = appState.get(slug);
852
+ if (!state) throw new Error(`unknown app: ${slug}`);
853
+ for (const key of ['appId', 'targetAppId', 'userId', 'profileKey', 'sessionId', 'mountNonce']) {
854
+ if (Object.prototype.hasOwnProperty.call(updates, key)) {
855
+ state[key] = updates[key] || null;
856
+ }
857
+ }
858
+ },
859
+ isBundleReady(slug) {
860
+ const state = appState.get(slug);
861
+ if (!state) throw new Error(`unknown app: ${slug}`);
862
+ return state.bundleReady;
863
+ },
864
+ waitForBundle(slug) {
865
+ const state = appState.get(slug);
866
+ if (!state) return Promise.reject(new Error(`unknown app: ${slug}`));
867
+ return state.bundleReadyPromise;
868
+ },
697
869
  async close() {
698
870
  for (const state of appState.values()) {
871
+ if (state.prepareTimer) clearTimeout(state.prepareTimer);
872
+ if (state.reloadTimer) clearTimeout(state.reloadTimer);
873
+ for (const watcher of state.sourceWatchers) {
874
+ try {
875
+ watcher.close();
876
+ } catch {
877
+ // ignore
878
+ }
879
+ }
880
+ state.sourceWatchers = [];
699
881
  if (state.watchPollTimer) clearTimeout(state.watchPollTimer);
700
882
  if (state.watcher) {
701
883
  try {
@@ -716,6 +898,14 @@ export async function startAppDevServer({
716
898
  state.buildProcess.kill('SIGTERM');
717
899
  }
718
900
  }
901
+ for (const res of hostSseClients) {
902
+ try {
903
+ res.end();
904
+ } catch {
905
+ // ignore
906
+ }
907
+ }
908
+ hostSseClients.clear();
719
909
  await new Promise((resolvePromise) => server.close(() => resolvePromise()));
720
910
  },
721
911
  };