@notis_ai/cli 0.2.18 → 0.2.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (395) hide show
  1. package/README.md +542 -146
  2. package/bin/check-runtime.js +15 -0
  3. package/bin/notis.js +2 -0
  4. package/config/notis_app_design_rules.json +320 -0
  5. package/config/portal-route-roots.json +45 -0
  6. package/dist/agent-hooks/notis-agent-hook.mjs +42325 -18273
  7. package/dist/base-skills/notis-cli/.notis-base-skill.json +1 -0
  8. package/dist/base-skills/notis-cli/SKILL.md +51 -280
  9. package/dist/base-skills/notis-cli/references/app-delivery.md +17 -0
  10. package/dist/base-skills/notis-cli/references/intelligence.md +94 -0
  11. package/dist/base-skills/notis-cli/references/native-databases.md +21 -0
  12. package/dist/base-skills/notis-cli/references/tool-examples.md +56 -0
  13. package/dist/base-skills/notis-cli/references/troubleshooting.md +67 -0
  14. package/dist/base-skills/notis-query/.notis-base-skill.json +1 -0
  15. package/dist/base-skills/notis-query/SKILL.md +35 -699
  16. package/dist/base-skills/notis-query/references/database-discovery.md +59 -0
  17. package/dist/base-skills/notis-query/references/documents.md +74 -0
  18. package/dist/base-skills/notis-query/references/query.md +76 -0
  19. package/dist/base-skills/notis-spaces/.notis-base-skill.json +1 -0
  20. package/dist/base-skills/notis-spaces/SKILL.md +218 -0
  21. package/dist/base-skills/notis-spaces/references/architecture.md +116 -0
  22. package/dist/base-skills/notis-spaces/references/context.md +81 -0
  23. package/dist/base-skills/notis-spaces/references/design.md +141 -0
  24. package/dist/base-skills/notis-spaces/references/fixtures.md +29 -0
  25. package/dist/base-skills/notis-spaces/references/memory.md +58 -0
  26. package/dist/base-skills/notis-spaces/references/reading.md +73 -0
  27. package/dist/base-skills/notis-spaces/references/release.md +120 -0
  28. package/dist/base-skills/notis-spaces/references/reports.md +77 -0
  29. package/dist/base-skills/notis-spaces/references/sdk.md +273 -0
  30. package/dist/base-skills/notis-spaces/references/store.md +118 -0
  31. package/dist/base-skills/notis-spaces/references/troubleshooting.md +28 -0
  32. package/dist/base-skills/notis-spaces/references/views.md +229 -0
  33. package/{template/packages → dist}/sdk/package.json +9 -10
  34. package/dist/sdk/src/agentContext.ts +36 -0
  35. package/dist/sdk/src/bundleModule.ts +175 -0
  36. package/dist/sdk/src/components/Dialog.tsx +103 -0
  37. package/dist/sdk/src/components/DocumentEditor.tsx +113 -0
  38. package/dist/sdk/src/components/DocumentPage.tsx +184 -0
  39. package/{template/packages → dist}/sdk/src/components/Markdown.tsx +9 -3
  40. package/{template/packages → dist}/sdk/src/components/MarkdownEditor.tsx +26 -5
  41. package/{template/packages → dist}/sdk/src/components/MultiSelectActionBar.tsx +95 -12
  42. package/{template/packages → dist}/sdk/src/components/MultiSelectCheckbox.tsx +4 -2
  43. package/dist/sdk/src/components/NotisCommentBoundary.tsx +172 -0
  44. package/{template/packages → dist}/sdk/src/components/NotisSelectionBoundary.tsx +9 -5
  45. package/dist/sdk/src/components/RecordComponents.tsx +111 -0
  46. package/dist/sdk/src/components/Skeleton.tsx +38 -0
  47. package/dist/sdk/src/config.ts +31 -0
  48. package/{template/packages → dist}/sdk/src/documents.ts +21 -6
  49. package/dist/sdk/src/hooks/useAction.ts +28 -0
  50. package/dist/sdk/src/hooks/useAgentContext.ts +23 -0
  51. package/{template/packages → dist}/sdk/src/hooks/useBackend.ts +3 -2
  52. package/dist/sdk/src/hooks/useCloudComputer.ts +94 -0
  53. package/dist/sdk/src/hooks/useCloudComputerShell.ts +114 -0
  54. package/{template/packages → dist}/sdk/src/hooks/useCollectionInteractions.ts +157 -147
  55. package/dist/sdk/src/hooks/useDatabaseSchema.ts +50 -0
  56. package/{template/packages → dist}/sdk/src/hooks/useDatabaseSubscription.ts +2 -2
  57. package/dist/sdk/src/hooks/useDocument.ts +51 -0
  58. package/{template/packages → dist}/sdk/src/hooks/useDocuments.ts +25 -59
  59. package/{template/packages → dist}/sdk/src/hooks/useHandover.ts +7 -7
  60. package/dist/sdk/src/hooks/useLongPressSelection.ts +79 -0
  61. package/dist/sdk/src/hooks/useMarqueeSelection.ts +194 -0
  62. package/{template/packages → dist}/sdk/src/hooks/useMultiSelect.ts +2 -8
  63. package/dist/sdk/src/hooks/useNativeDocumentBody.ts +50 -0
  64. package/dist/sdk/src/hooks/useNativeDocuments.ts +143 -0
  65. package/{template/packages → dist}/sdk/src/hooks/useNotis.ts +6 -1
  66. package/dist/sdk/src/hooks/useNotisNavigation.ts +70 -0
  67. package/dist/sdk/src/hooks/useQuery.ts +81 -0
  68. package/dist/sdk/src/hooks/useToolQuery.ts +12 -0
  69. package/dist/sdk/src/hooks/useTopBarSearch.ts +102 -0
  70. package/dist/sdk/src/hooks/useTopBarTitleLeaf.ts +75 -0
  71. package/{template/packages → dist}/sdk/src/hooks/useUpsertDocument.ts +1 -1
  72. package/dist/sdk/src/hooks/useViewParams.ts +195 -0
  73. package/dist/sdk/src/hooks/useViewerReads.ts +129 -0
  74. package/{template/packages → dist}/sdk/src/index.ts +64 -1
  75. package/{template/packages → dist}/sdk/src/interactions/actions.ts +14 -1
  76. package/{template/packages → dist}/sdk/src/interactions/shortcuts.tsx +129 -23
  77. package/dist/sdk/src/interactions/visibility.ts +13 -0
  78. package/{template/packages → dist}/sdk/src/interactions.ts +5 -1
  79. package/dist/sdk/src/nativeData.ts +39 -0
  80. package/{template/packages → dist}/sdk/src/provider.tsx +9 -4
  81. package/dist/sdk/src/queryCache.ts +292 -0
  82. package/dist/sdk/src/queryErrors.ts +51 -0
  83. package/dist/sdk/src/runtime.ts +675 -0
  84. package/dist/sdk/src/runtimeModules.ts +33 -0
  85. package/dist/sdk/src/space.ts +442 -0
  86. package/dist/sdk/src/standardViewParams.ts +36 -0
  87. package/{template/packages → dist}/sdk/src/styles.css +81 -16
  88. package/dist/sdk/src/tailwind.ts +66 -0
  89. package/dist/sdk/src/ui/charts/RenderChartData.tsx +25 -0
  90. package/dist/sdk/src/ui/engine/core.ts +392 -0
  91. package/dist/sdk/src/ui/engine/dom.ts +183 -0
  92. package/dist/sdk/src/ui/engine/hooks.ts +195 -0
  93. package/dist/sdk/src/ui/engine/instance.ts +63 -0
  94. package/dist/sdk/src/ui/engine/keycapContext.ts +2 -0
  95. package/dist/sdk/src/ui/engine/registry.ts +262 -0
  96. package/dist/sdk/src/ui/engine/types.ts +62 -0
  97. package/dist/sdk/src/ui/icons.tsx +91 -0
  98. package/dist/sdk/src/ui/util.ts +83 -0
  99. package/dist/sdk/src/viewPeriod.ts +26 -0
  100. package/dist/sdk/src/vite.ts +92 -0
  101. package/dist/sdk/src/words/actions.ts +295 -0
  102. package/dist/sdk/src/words/clashes.ts +158 -0
  103. package/dist/sdk/src/words/format.ts +136 -0
  104. package/dist/sdk/src/words/index.ts +13 -0
  105. package/dist/sdk/src/words/keymap.ts +72 -0
  106. package/dist/sdk/src/words/keys.ts +297 -0
  107. package/dist/sdk/src/words/shell.ts +46 -0
  108. package/dist/sdk/src/words/types.ts +65 -0
  109. package/dist/skill-sync/index.js +832 -151
  110. package/dist/skill-sync/index.js.map +4 -4
  111. package/dist/skill-sync-worker.mjs +3941 -0
  112. package/dist/space-harness/host.js +1460 -0
  113. package/dist/space-sdk/dist/chunk-ZSN57ATH.js +5 -0
  114. package/dist/space-sdk/dist/index.js +15 -0
  115. package/dist/space-sdk/dist/ui.js +18 -0
  116. package/dist/space-sdk/package.json +33 -0
  117. package/dist/space-sdk/src/agentContext.d.ts +36 -0
  118. package/dist/space-sdk/src/components/Dialog.d.ts +20 -0
  119. package/dist/space-sdk/src/components/DocumentEditor.d.ts +46 -0
  120. package/dist/space-sdk/src/components/DocumentPage.d.ts +53 -0
  121. package/dist/space-sdk/src/components/Markdown.d.ts +18 -0
  122. package/dist/space-sdk/src/components/MarkdownEditor.d.ts +11 -0
  123. package/dist/space-sdk/src/components/MultiSelectActionBar.d.ts +31 -0
  124. package/dist/space-sdk/src/components/MultiSelectCheckbox.d.ts +14 -0
  125. package/dist/space-sdk/src/components/MultiSelectDragOverlay.d.ts +8 -0
  126. package/dist/space-sdk/src/components/NotisCommentBoundary.d.ts +36 -0
  127. package/dist/space-sdk/src/components/NotisSelectionBoundary.d.ts +16 -0
  128. package/dist/space-sdk/src/components/RecordComponents.d.ts +64 -0
  129. package/dist/space-sdk/src/components/ShortcutHints.d.ts +12 -0
  130. package/dist/space-sdk/src/components/Skeleton.d.ts +23 -0
  131. package/dist/space-sdk/src/config.ts +31 -0
  132. package/dist/space-sdk/src/documents.d.ts +59 -0
  133. package/dist/space-sdk/src/hooks/useAction.d.ts +8 -0
  134. package/dist/space-sdk/src/hooks/useActiveResource.d.ts +3 -0
  135. package/dist/space-sdk/src/hooks/useAgentContext.d.ts +7 -0
  136. package/dist/space-sdk/src/hooks/useBackend.d.ts +23 -0
  137. package/dist/space-sdk/src/hooks/useCloudComputer.d.ts +45 -0
  138. package/dist/space-sdk/src/hooks/useCloudComputerShell.d.ts +49 -0
  139. package/dist/space-sdk/src/hooks/useCollectionInteractions.d.ts +118 -0
  140. package/dist/space-sdk/src/hooks/useDatabaseSchema.d.ts +14 -0
  141. package/dist/space-sdk/src/hooks/useDatabaseSubscription.d.ts +26 -0
  142. package/dist/space-sdk/src/hooks/useDocument.d.ts +22 -0
  143. package/dist/space-sdk/src/hooks/useDocuments.d.ts +31 -0
  144. package/dist/space-sdk/src/hooks/useHandover.d.ts +43 -0
  145. package/dist/space-sdk/src/hooks/useLongPressSelection.d.ts +9 -0
  146. package/dist/space-sdk/src/hooks/useMarqueeSelection.d.ts +57 -0
  147. package/dist/space-sdk/src/hooks/useMultiSelect.d.ts +51 -0
  148. package/dist/space-sdk/src/hooks/useNativeDocumentBody.d.ts +24 -0
  149. package/dist/space-sdk/src/hooks/useNativeDocuments.d.ts +55 -0
  150. package/dist/space-sdk/src/hooks/useNotis.d.ts +25 -0
  151. package/dist/space-sdk/src/hooks/useNotisNavigation.d.ts +33 -0
  152. package/dist/space-sdk/src/hooks/useQuery.d.ts +22 -0
  153. package/dist/space-sdk/src/hooks/useTool.d.ts +20 -0
  154. package/dist/space-sdk/src/hooks/useToolQuery.d.ts +3 -0
  155. package/dist/space-sdk/src/hooks/useTools.d.ts +15 -0
  156. package/dist/space-sdk/src/hooks/useTopBarSearch.d.ts +48 -0
  157. package/dist/space-sdk/src/hooks/useTopBarTitleLeaf.d.ts +24 -0
  158. package/dist/space-sdk/src/hooks/useUpsertDocument.d.ts +27 -0
  159. package/dist/space-sdk/src/hooks/useViewParams.d.ts +76 -0
  160. package/dist/space-sdk/src/hooks/useViewerReads.d.ts +38 -0
  161. package/dist/space-sdk/src/index.d.ts +98 -0
  162. package/dist/space-sdk/src/interactions/actions.d.ts +33 -0
  163. package/dist/space-sdk/src/interactions/shortcuts.d.ts +42 -0
  164. package/dist/space-sdk/src/interactions/visibility.d.ts +2 -0
  165. package/dist/space-sdk/src/nativeData.d.ts +66 -0
  166. package/dist/space-sdk/src/provider.d.ts +17 -0
  167. package/dist/space-sdk/src/queryCache.d.ts +45 -0
  168. package/dist/space-sdk/src/queryErrors.ts +51 -0
  169. package/dist/space-sdk/src/runtime.d.ts +615 -0
  170. package/dist/space-sdk/src/runtimeModules.ts +33 -0
  171. package/dist/space-sdk/src/space.ts +442 -0
  172. package/dist/space-sdk/src/standardViewParams.d.ts +15 -0
  173. package/dist/space-sdk/src/tailwind-candidates.d.ts +3418 -0
  174. package/dist/space-sdk/src/tailwind.ts +66 -0
  175. package/dist/space-sdk/src/ui/actions/ActionToast.d.ts +15 -0
  176. package/dist/space-sdk/src/ui/actions/actionWords.d.ts +16 -0
  177. package/dist/space-sdk/src/ui/actions/index.d.ts +7 -0
  178. package/dist/space-sdk/src/ui/actions/useActions.d.ts +69 -0
  179. package/dist/space-sdk/src/ui/actions/useToast.d.ts +41 -0
  180. package/dist/space-sdk/src/ui/blocks/Tabs.d.ts +139 -0
  181. package/dist/space-sdk/src/ui/blocks/blocks.d.ts +217 -0
  182. package/dist/space-sdk/src/ui/blocks/index.d.ts +4 -0
  183. package/dist/space-sdk/src/ui/board/BoardView.d.ts +36 -0
  184. package/dist/space-sdk/src/ui/board/boardModel.d.ts +46 -0
  185. package/dist/space-sdk/src/ui/board/boardWords.d.ts +9 -0
  186. package/dist/space-sdk/src/ui/board/index.d.ts +5 -0
  187. package/dist/space-sdk/src/ui/calendar/CalendarView.d.ts +49 -0
  188. package/dist/space-sdk/src/ui/calendar/DayPicker.d.ts +23 -0
  189. package/dist/space-sdk/src/ui/calendar/calendarMath.d.ts +81 -0
  190. package/dist/space-sdk/src/ui/charts/Chart.d.ts +203 -0
  191. package/dist/space-sdk/src/ui/charts/ChartTable.d.ts +37 -0
  192. package/dist/space-sdk/src/ui/charts/RenderChartData.d.ts +16 -0
  193. package/dist/space-sdk/src/ui/charts/StatRow.d.ts +59 -0
  194. package/dist/space-sdk/src/ui/charts/chartData.d.ts +84 -0
  195. package/dist/space-sdk/src/ui/comments/CommentPane.d.ts +58 -0
  196. package/dist/space-sdk/src/ui/comments/commentModel.d.ts +157 -0
  197. package/dist/space-sdk/src/ui/comments/commentWords.d.ts +60 -0
  198. package/dist/space-sdk/src/ui/comments/index.d.ts +4 -0
  199. package/dist/space-sdk/src/ui/comments/useComments.d.ts +146 -0
  200. package/dist/space-sdk/src/ui/engine/HelpSheet.d.ts +12 -0
  201. package/dist/space-sdk/src/ui/engine/KeyboardEngine.d.ts +27 -0
  202. package/dist/space-sdk/src/ui/engine/core.d.ts +73 -0
  203. package/dist/space-sdk/src/ui/engine/hooks.d.ts +64 -0
  204. package/dist/space-sdk/src/ui/engine/index.d.ts +14 -0
  205. package/dist/space-sdk/src/ui/engine/instance.d.ts +21 -0
  206. package/dist/space-sdk/src/ui/engine/keycapContext.d.ts +2 -0
  207. package/dist/space-sdk/src/ui/engine/registry.d.ts +77 -0
  208. package/dist/space-sdk/src/ui/engine/step.d.ts +11 -0
  209. package/dist/space-sdk/src/ui/engine/types.d.ts +54 -0
  210. package/dist/space-sdk/src/ui/fields/fieldWords.d.ts +56 -0
  211. package/dist/space-sdk/src/ui/fields/fields.d.ts +162 -0
  212. package/dist/space-sdk/src/ui/fields/index.d.ts +5 -0
  213. package/dist/space-sdk/src/ui/fields/useFieldDraft.d.ts +22 -0
  214. package/dist/space-sdk/src/ui/gallery/AttachmentGallery.d.ts +53 -0
  215. package/dist/space-sdk/src/ui/gallery/ImageViewer.d.ts +27 -0
  216. package/dist/space-sdk/src/ui/gallery/galleryWords.d.ts +95 -0
  217. package/dist/space-sdk/src/ui/gallery/index.d.ts +5 -0
  218. package/dist/space-sdk/src/ui/icons.d.ts +13 -0
  219. package/dist/space-sdk/src/ui/index.d.ts +54 -0
  220. package/dist/space-sdk/src/ui/keycap/Keycap.d.ts +22 -0
  221. package/dist/space-sdk/src/ui/keycap/index.d.ts +2 -0
  222. package/dist/space-sdk/src/ui/layout/Panels.d.ts +50 -0
  223. package/dist/space-sdk/src/ui/layout/chatPanelEdge.d.ts +77 -0
  224. package/dist/space-sdk/src/ui/layout/index.d.ts +5 -0
  225. package/dist/space-sdk/src/ui/layout/panelMath.d.ts +91 -0
  226. package/dist/space-sdk/src/ui/list/ListReader.d.ts +380 -0
  227. package/dist/space-sdk/src/ui/list/Row.d.ts +145 -0
  228. package/dist/space-sdk/src/ui/list/SelectionBar.d.ts +65 -0
  229. package/dist/space-sdk/src/ui/list/index.d.ts +14 -0
  230. package/dist/space-sdk/src/ui/list/rowState.d.ts +22 -0
  231. package/dist/space-sdk/src/ui/list/useListKeys.d.ts +89 -0
  232. package/dist/space-sdk/src/ui/list/useReorder.d.ts +86 -0
  233. package/dist/space-sdk/src/ui/list/useSelection.d.ts +90 -0
  234. package/dist/space-sdk/src/ui/open/OpenAsMenu.d.ts +15 -0
  235. package/dist/space-sdk/src/ui/open/index.d.ts +6 -0
  236. package/dist/space-sdk/src/ui/open/openModeStore.d.ts +39 -0
  237. package/dist/space-sdk/src/ui/open/useOpenMode.d.ts +17 -0
  238. package/dist/space-sdk/src/ui/overlay/Menu.d.ts +88 -0
  239. package/dist/space-sdk/src/ui/overlay/Popover.d.ts +28 -0
  240. package/dist/space-sdk/src/ui/overlay/Tooltip.d.ts +19 -0
  241. package/dist/space-sdk/src/ui/overlay/index.d.ts +9 -0
  242. package/dist/space-sdk/src/ui/overlay/layerRoot.d.ts +24 -0
  243. package/dist/space-sdk/src/ui/overlay/position.d.ts +31 -0
  244. package/dist/space-sdk/src/ui/period/PeriodPicker.d.ts +30 -0
  245. package/dist/space-sdk/src/ui/period/dateFormat.d.ts +50 -0
  246. package/dist/space-sdk/src/ui/period/dateWords.d.ts +88 -0
  247. package/dist/space-sdk/src/ui/period/periods.d.ts +70 -0
  248. package/dist/space-sdk/src/ui/period/usePeriod.d.ts +23 -0
  249. package/dist/space-sdk/src/ui/present/index.d.ts +3 -0
  250. package/dist/space-sdk/src/ui/present/presentWords.d.ts +27 -0
  251. package/dist/space-sdk/src/ui/present/screens.d.ts +36 -0
  252. package/dist/space-sdk/src/ui/present/useAudienceScreen.d.ts +38 -0
  253. package/dist/space-sdk/src/ui/styles.d.ts +21 -0
  254. package/dist/space-sdk/src/ui/timeline/TimelineView.d.ts +44 -0
  255. package/dist/space-sdk/src/ui/timeline/timelineMath.d.ts +123 -0
  256. package/dist/space-sdk/src/viewPeriod.d.ts +8 -0
  257. package/dist/space-sdk/src/vite.ts +92 -0
  258. package/dist/space-sdk/src/words/actions.ts +295 -0
  259. package/dist/space-sdk/src/words/clashes.ts +158 -0
  260. package/dist/space-sdk/src/words/format.ts +136 -0
  261. package/dist/space-sdk/src/words/index.ts +13 -0
  262. package/dist/space-sdk/src/words/keymap.ts +72 -0
  263. package/dist/space-sdk/src/words/keys.ts +297 -0
  264. package/dist/space-sdk/src/words/shell.ts +46 -0
  265. package/dist/space-sdk/src/words/types.ts +65 -0
  266. package/dist/view-renderer/dist/host.css +1 -0
  267. package/dist/view-renderer/dist/host.js +2374 -0
  268. package/dist/view-renderer/dist/theme.css +73 -0
  269. package/dist/view-renderer/src/dom.js +212 -0
  270. package/dist/view-renderer/src/errors.js +8 -0
  271. package/dist/view-renderer/src/host.tsx +250 -0
  272. package/dist/view-renderer/src/index.js +4 -0
  273. package/dist/view-renderer/src/png.js +213 -0
  274. package/dist/view-renderer/src/pool.js +110 -0
  275. package/dist/view-renderer/src/remote-broker.js +59 -0
  276. package/dist/view-renderer/src/renderer.js +279 -0
  277. package/package.json +21 -11
  278. package/skills/notis-cli/AGENT_INSTRUCTIONS.md +63 -12
  279. package/skills/notis-onboarding/BRIEF.md +14 -9
  280. package/skills/notis-query/cli.md +1 -1
  281. package/skills/notis-spaces/cli.md +540 -0
  282. package/src/cli.js +8 -8
  283. package/src/command-specs/agents.js +121 -8
  284. package/src/command-specs/auth.js +3 -3
  285. package/src/command-specs/diagnostics.js +97 -20
  286. package/src/command-specs/documents.js +158 -0
  287. package/src/command-specs/files.js +29 -0
  288. package/src/command-specs/handover.js +331 -79
  289. package/src/command-specs/helpers.js +50 -40
  290. package/src/command-specs/index.js +14 -5
  291. package/src/command-specs/meta.js +38 -9
  292. package/src/command-specs/onboarding.js +1 -1
  293. package/src/command-specs/profile.js +42 -1
  294. package/src/command-specs/skills.js +314 -9
  295. package/src/command-specs/smoke.js +16 -4
  296. package/src/command-specs/spaces.js +783 -0
  297. package/src/command-specs/tools.js +118 -21
  298. package/src/command-specs/version-history.js +20 -0
  299. package/src/command-specs/views.js +69 -0
  300. package/src/runtime/agent-browser.js +178 -267
  301. package/src/runtime/agent-setup.js +172 -13
  302. package/src/runtime/app-boundary-validator.js +338 -2
  303. package/src/runtime/app-data-literals.js +82 -0
  304. package/src/runtime/app-platform.js +750 -1570
  305. package/src/runtime/auth-recovery.js +17 -0
  306. package/src/runtime/base-skills.d.ts +11 -0
  307. package/src/runtime/base-skills.js +100 -3
  308. package/src/runtime/computer-identity.d.ts +30 -0
  309. package/src/runtime/computer-identity.js +151 -0
  310. package/src/runtime/config-lock.js +135 -0
  311. package/src/runtime/confined-space-build.js +74 -0
  312. package/src/runtime/confined-space-verify.js +49 -0
  313. package/src/runtime/delegated-context.js +5 -21
  314. package/src/runtime/native-record-files.js +47 -0
  315. package/src/runtime/oauth.js +342 -31
  316. package/src/runtime/profiles.js +19 -112
  317. package/src/runtime/retired-sdk-files.js +24 -0
  318. package/src/runtime/retry.js +150 -0
  319. package/src/runtime/skill-sync/cloud-client.ts +33 -8
  320. package/src/runtime/skill-sync/index.ts +443 -106
  321. package/src/runtime/skill-sync/local-scanner.ts +248 -32
  322. package/src/runtime/skill-sync/native-identity.ts +102 -0
  323. package/src/runtime/skill-sync/native-plan.ts +70 -0
  324. package/src/runtime/skill-sync/symlink-manager.ts +66 -16
  325. package/src/runtime/skill-sync/sync-plan.ts +34 -2
  326. package/src/runtime/skill-sync/types.ts +54 -0
  327. package/src/runtime/skill-sync/write-cloud-skill.ts +4 -4
  328. package/src/runtime/skill-sync-service.js +109 -0
  329. package/src/runtime/source-root.js +32 -0
  330. package/src/runtime/space-collection-scaffold.js +107 -0
  331. package/src/runtime/space-database-structures.js +229 -0
  332. package/src/runtime/space-fixture-broker.js +68 -0
  333. package/src/runtime/space-lock.js +337 -0
  334. package/src/runtime/space-platform.js +133 -0
  335. package/src/runtime/space-source.js +290 -0
  336. package/src/runtime/space-test-server.js +354 -0
  337. package/src/runtime/space-verification.js +94 -0
  338. package/src/runtime/space-verify-browser-worker.js +51 -0
  339. package/src/runtime/space-view-manifest.js +368 -0
  340. package/src/runtime/sync-skills.d.ts +5 -1
  341. package/src/runtime/sync-skills.js +11 -2
  342. package/src/runtime/tool-confirmation.js +138 -0
  343. package/src/runtime/transport.js +118 -24
  344. package/src/runtime/upsell.js +219 -0
  345. package/src/runtime/view-render.js +49 -0
  346. package/src/skill-sync-worker-entry.js +2 -0
  347. package/src/skill-sync-worker.js +114 -0
  348. package/src/space-harness-host.ts +122 -0
  349. package/template/.harness/index.html.tmpl +9 -352
  350. package/template/app/globals.css +29 -3
  351. package/template/app/layout.tsx +2 -6
  352. package/template/components/ui/badge.tsx +7 -4
  353. package/template/components/ui/button.tsx +1 -1
  354. package/template/components/ui/native-select.tsx +24 -0
  355. package/template/notis.config.ts +4 -36
  356. package/template/package-lock.json +1800 -1867
  357. package/template/package.json +16 -14
  358. package/template/postcss.config.mjs +1 -1
  359. package/template/tailwind.config.ts +1 -6
  360. package/template/tsconfig.json +1 -0
  361. package/template/vite.config.ts +2 -2
  362. package/dist/base-skills/notis-apps/SKILL.md +0 -613
  363. package/skills/notis-apps/cli.md +0 -285
  364. package/src/command-specs/apps.js +0 -3011
  365. package/src/runtime/app-changelog.js +0 -79
  366. package/src/runtime/app-dev-consumers.js +0 -154
  367. package/src/runtime/app-dev-host-lock.js +0 -80
  368. package/src/runtime/app-dev-process-identity.js +0 -111
  369. package/src/runtime/app-dev-roots.js +0 -284
  370. package/src/runtime/app-dev-server.js +0 -1140
  371. package/src/runtime/app-dev-sessions.js +0 -185
  372. package/src/runtime/app-registry-scaffolds.js +0 -367
  373. package/src/runtime/assets/store-screenshot-dark.png +0 -0
  374. package/src/runtime/cli-mode.generated.js +0 -5
  375. package/src/runtime/cli-mode.js +0 -34
  376. package/src/runtime/store-screenshot.js +0 -143
  377. package/template/CHANGELOG.md +0 -5
  378. package/template/app/page.tsx +0 -60
  379. package/template/components/ui/card.tsx +0 -56
  380. package/template/packages/sdk/src/components/DocumentEditor.tsx +0 -103
  381. package/template/packages/sdk/src/config.ts +0 -259
  382. package/template/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
  383. package/template/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
  384. package/template/packages/sdk/src/hooks/useDocument.ts +0 -78
  385. package/template/packages/sdk/src/hooks/useNotisNavigation.ts +0 -52
  386. package/template/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
  387. package/template/packages/sdk/src/runtime.ts +0 -439
  388. package/template/packages/sdk/src/vite.ts +0 -56
  389. package/template/packages/sdk/tsconfig.json +0 -15
  390. /package/{template/packages → dist}/sdk/src/components/MultiSelectDragOverlay.tsx +0 -0
  391. /package/{template/packages → dist}/sdk/src/components/ShortcutHints.tsx +0 -0
  392. /package/{template/packages → dist}/sdk/src/hooks/useActiveResource.ts +0 -0
  393. /package/{template/packages → dist}/sdk/src/hooks/useTool.ts +0 -0
  394. /package/{template/packages → dist}/sdk/src/hooks/useTools.ts +0 -0
  395. /package/{template/packages → dist}/sdk/src/ui.ts +0 -0
@@ -1,109 +1,7 @@
1
1
  import { spawn, spawnSync } from 'node:child_process';
2
- import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { mkdirSync, writeFileSync } from 'node:fs';
3
3
  import { dirname } from 'node:path';
4
4
 
5
- export const HIDE_HARNESS_STATUS_SCRIPT =
6
- "(() => { const el = document.getElementById('harness-status'); if (el) el.style.opacity = '0'; return true; })()";
7
-
8
- /**
9
- * Prepare the mounted harness page for a listing capture: remove the debug
10
- * banner and the layout the harness adds around the app (top padding for the
11
- * banner, 100vh min-height) so content height can be measured and framed
12
- * without artificial whitespace.
13
- */
14
- const PREPARE_CAPTURE_SCRIPT =
15
- "(() => { const s = document.getElementById('harness-status'); if (s) s.style.display = 'none';" +
16
- " const r = document.getElementById('root'); if (r) { r.style.paddingTop = '0'; r.style.minHeight = '0'; }" +
17
- " document.body.style.minHeight = '0'; document.documentElement.style.minHeight = '0'; return true; })()";
18
-
19
- const FONTS_STATUS_SCRIPT =
20
- "(() => (document.fonts ? document.fonts.status : 'loaded'))()";
21
-
22
- const CONTENT_HEIGHT_SCRIPT =
23
- '(() => { const r = document.getElementById(\'root\');' +
24
- ' if (!r) return Math.ceil(document.documentElement.scrollHeight);' +
25
- ' return Math.ceil(Math.max(r.scrollHeight, r.getBoundingClientRect().height)); })()';
26
-
27
- // CSS viewport bounds for content-aware framing. Content shorter than the
28
- // minimum gets a little breathing room; content taller than the maximum is
29
- // cropped at the frame rather than shrunk into unreadably small text.
30
- const MIN_CSS_VIEWPORT_HEIGHT = 720;
31
- const MAX_CSS_VIEWPORT_HEIGHT = 1500;
32
-
33
- function pngDimensions(path) {
34
- try {
35
- const buffer = readFileSync(path);
36
- if (buffer.length < 24 || buffer.readUInt32BE(12) !== 0x49484452) {
37
- return null;
38
- }
39
- return { width: buffer.readUInt32BE(16), height: buffer.readUInt32BE(20) };
40
- } catch {
41
- return null;
42
- }
43
- }
44
-
45
- /**
46
- * Pick a CSS viewport that frames `contentHeight` at the output aspect ratio,
47
- * with a deviceScaleFactor that maps it back to exactly outputWidth x
48
- * outputHeight physical pixels. Width snaps to a multiple of 8 so the height
49
- * stays integral for the common 16:10 target.
50
- */
51
- function fitCssViewport(contentHeight, outputWidth, outputHeight) {
52
- const aspect = outputWidth / outputHeight;
53
- const cssH = Math.min(
54
- MAX_CSS_VIEWPORT_HEIGHT,
55
- Math.max(MIN_CSS_VIEWPORT_HEIGHT, Math.round(contentHeight)),
56
- );
57
- const cssW = Math.round((cssH * aspect) / 8) * 8;
58
- return {
59
- width: cssW,
60
- height: Math.round(cssW / aspect),
61
- scale: outputWidth / cssW,
62
- };
63
- }
64
-
65
- async function evalNumber(sessionName, script, fallback = null) {
66
- const result = await runAgentBrowser(
67
- ['--session', sessionName, '--json', 'eval', script],
68
- { timeoutMs: 5000 },
69
- );
70
- if (result.exitCode !== 0) {
71
- return fallback;
72
- }
73
- try {
74
- const payload = parseAgentBrowserJson(result.stdout);
75
- const raw = payload?.data?.result ?? payload?.result ?? payload?.data ?? null;
76
- const value = typeof raw === 'string' ? JSON.parse(raw) : raw;
77
- return Number.isFinite(value) ? value : fallback;
78
- } catch {
79
- return fallback;
80
- }
81
- }
82
-
83
- async function waitForFonts(sessionName, timeoutMs = 3000) {
84
- const deadline = Date.now() + timeoutMs;
85
- while (Date.now() < deadline) {
86
- const result = await runAgentBrowser(
87
- ['--session', sessionName, '--json', 'eval', FONTS_STATUS_SCRIPT],
88
- { timeoutMs: 5000 },
89
- );
90
- if (result.exitCode !== 0) {
91
- return;
92
- }
93
- try {
94
- const payload = parseAgentBrowserJson(result.stdout);
95
- const raw = payload?.data?.result ?? payload?.result ?? payload?.data ?? null;
96
- const status = typeof raw === 'string' && raw.startsWith('"') ? JSON.parse(raw) : raw;
97
- if (status !== 'loading') {
98
- return;
99
- }
100
- } catch {
101
- return;
102
- }
103
- await delay(150);
104
- }
105
- }
106
-
107
5
  async function setViewport(sessionName, width, height, scale = null) {
108
6
  const args = ['--session', sessionName, 'set', 'viewport', String(width), String(height)];
109
7
  if (scale != null) {
@@ -135,9 +33,11 @@ function runAgentBrowser(args, { timeoutMs = 30_000 } = {}) {
135
33
  let stdout = '';
136
34
  let stderr = '';
137
35
  let timedOut = false;
36
+ let hardStop;
138
37
  const timer = setTimeout(() => {
139
38
  timedOut = true;
140
39
  child.kill('SIGTERM');
40
+ hardStop = setTimeout(() => child.kill('SIGKILL'), 2000);
141
41
  }, timeoutMs);
142
42
 
143
43
  child.stdout.on('data', (chunk) => {
@@ -147,7 +47,7 @@ function runAgentBrowser(args, { timeoutMs = 30_000 } = {}) {
147
47
  stderr += chunk.toString();
148
48
  });
149
49
  child.on('error', (error) => {
150
- clearTimeout(timer);
50
+ clearTimeout(timer); clearTimeout(hardStop);
151
51
  resolvePromise({
152
52
  exitCode: 1,
153
53
  stdout,
@@ -156,7 +56,7 @@ function runAgentBrowser(args, { timeoutMs = 30_000 } = {}) {
156
56
  });
157
57
  });
158
58
  child.on('close', (code) => {
159
- clearTimeout(timer);
59
+ clearTimeout(timer); clearTimeout(hardStop);
160
60
  resolvePromise({
161
61
  exitCode: timedOut ? 124 : (code ?? 1),
162
62
  stdout,
@@ -227,10 +127,19 @@ export async function runHarnessRoute({
227
127
  sessionName,
228
128
  timeoutMs = 10_000,
229
129
  snapshotPath = null,
130
+ designViewports = DEFAULT_DESIGN_VIEWPORTS,
131
+ waitForRuntime = false,
132
+ signal,
230
133
  }) {
134
+ const cancelled = () => ({ mounted: false, renderStarted: false, runtimeCalls: [],
135
+ errors: [{ phase: 'browser', message: 'Verification cancelled.' }], snapshotPath: null, tool_error: null });
136
+ if (signal?.aborted) return cancelled();
231
137
  const opened = await runAgentBrowser(['--session', sessionName, 'open', url], {
232
138
  timeoutMs: Math.min(Math.max(timeoutMs, 5000), 30_000),
233
139
  });
140
+ // Let an in-flight open settle before the owner closes its browser. Closing
141
+ // concurrently with open can leave a newly launched browser behind.
142
+ if (signal?.aborted) return cancelled();
234
143
  if (opened.exitCode !== 0) {
235
144
  return {
236
145
  mounted: false,
@@ -243,20 +152,26 @@ export async function runHarnessRoute({
243
152
  }
244
153
 
245
154
  const deadline = Date.now() + timeoutMs;
246
- while (Date.now() < deadline) {
155
+ let readySince = null;
156
+ while (Date.now() < deadline && !signal?.aborted) {
247
157
  const lastRead = await readHarness(sessionName, 5000);
248
158
  if (!lastRead.ok) {
159
+ readySince = null;
249
160
  await delay(250);
250
161
  continue;
251
162
  }
252
163
  const harness = lastRead.harness;
253
- if (harness?.mounted || (Array.isArray(harness?.errors) && harness.errors.length > 0)) {
254
- await delay(250);
255
- break;
256
- }
164
+ if (Array.isArray(harness?.errors) && harness.errors.length > 0) break;
165
+ const pending = (harness?.runtimeCalls || []).some(call => call?.ok == null);
166
+ if (harness?.mounted && (!waitForRuntime || !pending)) {
167
+ readySince ??= Date.now();
168
+ if (Date.now() - readySince >= 250) break;
169
+ } else readySince = null;
257
170
  await delay(250);
258
171
  }
259
172
 
173
+ if (signal?.aborted) return cancelled();
174
+
260
175
  const finalRead = await readHarness(sessionName, 5000);
261
176
  if (!finalRead.ok) {
262
177
  return {
@@ -282,183 +197,179 @@ export async function runHarnessRoute({
282
197
  }
283
198
  }
284
199
 
285
- const timedOut = !harness.mounted && Date.now() >= deadline;
200
+ const runtimeCalls = Array.isArray(harness.runtimeCalls) ? harness.runtimeCalls : [];
201
+ const timedOut = (!harness.mounted || (waitForRuntime && runtimeCalls.some(call => call?.ok == null))) && Date.now() >= deadline;
202
+
203
+ // Design assertions only mean something on a mounted route. Loading
204
+ // placeholders are excused while a runtime call is still in flight.
205
+ let design = [];
206
+ let designToolError = null;
207
+ if (harness.mounted && Array.isArray(designViewports) && designViewports.length > 0) {
208
+ await delay(500);
209
+ const pending = runtimeCalls.some((call) => call && call.ok == null);
210
+ const collected = await collectDesignFindings(sessionName, {
211
+ viewports: designViewports,
212
+ skipLoadingCheck: pending,
213
+ });
214
+ design = collected.findings;
215
+ designToolError = collected.tool_error;
216
+ }
217
+
286
218
  return {
287
219
  mounted: Boolean(harness.mounted),
288
220
  renderStarted: Boolean(harness.renderStarted),
289
221
  errors: Array.isArray(harness.errors) ? harness.errors : [],
290
- runtimeCalls: Array.isArray(harness.runtimeCalls) ? harness.runtimeCalls : [],
222
+ runtimeCalls,
291
223
  snapshotPath: savedSnapshotPath,
292
224
  timed_out: timedOut,
293
225
  tool_error: null,
226
+ design,
227
+ design_tool_error: designToolError,
294
228
  raw: harness,
295
229
  };
296
230
  }
297
231
 
298
- /**
299
- * Open a harness route, wait for it to mount, then capture a PNG screenshot of
300
- * the rendered app. Output is always exactly `width` x `height` physical
301
- * pixels (default 2000x1250, the 16:10 the listing validator expects), but the
302
- * CSS viewport is fitted to the rendered content height: short pages render
303
- * larger (no dead whitespace below the app), tall pages get more room before
304
- * cropping. The deviceScaleFactor maps the fitted CSS viewport back onto the
305
- * fixed output frame. Used by `notis apps screenshot`.
306
- */
307
- export async function captureHarnessScreenshot({
308
- url,
309
- sessionName,
310
- screenshotPath,
311
- focusSelector = null,
312
- width = 2000,
313
- height = 1250,
314
- timeoutMs = 15_000,
315
- }) {
316
- const opened = await runAgentBrowser(['--session', sessionName, 'open', url], {
317
- timeoutMs: Math.min(Math.max(timeoutMs, 5000), 30_000),
318
- });
319
- if (opened.exitCode !== 0) {
320
- return { ok: false, mounted: false, screenshotPath: null, tool_error: commandError('open', opened) };
321
- }
322
-
323
- // Initial fixed frame so mount/layout happen at a deterministic size; the
324
- // scale keeps physical output at width x height from the very first paint.
325
- const initial = fitCssViewport(900, width, height);
326
- let scaledViewport = await setViewport(
327
- sessionName,
328
- initial.width,
329
- initial.height,
330
- initial.scale,
331
- );
332
- if (!scaledViewport) {
333
- // Older agent-browser without deviceScaleFactor support: fall back to a
334
- // plain fixed viewport at output size.
335
- await setViewport(sessionName, width, height);
336
- }
232
+ export async function closeAgentBrowserSessionResult(sessionName) {
233
+ const started = Date.now();
234
+ // agent-browser waits up to 30s for CDP Browser.close and then up to 5s
235
+ // for its owned Chrome process. Do not kill the client at the start of the
236
+ // provider's shutdown grace period and lose its actual close acknowledgement.
237
+ const result = await runAgentBrowser(['--session', sessionName, '--json', 'close'], { timeoutMs: 40_000 });
238
+ let acknowledged = false;
239
+ try {
240
+ const reply = parseAgentBrowserJson(result.stdout);
241
+ acknowledged = reply?.success === true && reply?.data?.closed === true;
242
+ } catch { /* Missing acknowledgement is a failed cleanup, never a pass. */ }
243
+ return { ok: result.exitCode === 0 && acknowledged, exit_code: result.exitCode,
244
+ timed_out: Boolean(result.timedOut), duration_ms: Date.now() - started };
245
+ }
337
246
 
338
- const deadline = Date.now() + timeoutMs;
339
- let lastErrors = [];
340
- let lastReadError = null;
341
- let mounted = false;
342
- while (Date.now() < deadline) {
343
- const read = await readHarness(sessionName, 5000);
344
- if (read.ok) {
345
- const harness = read.harness || {};
346
- mounted = Boolean(harness.mounted);
347
- lastErrors = Array.isArray(harness.errors) ? harness.errors : [];
348
- if (mounted || lastErrors.length > 0) {
349
- break;
350
- }
351
- } else {
352
- lastReadError = read.toolError;
247
+ // ---------------------------------------------------------------------------
248
+ // Runtime design assertions.
249
+ //
250
+ // The static design lint (app-boundary-validator.js) catches banned class
251
+ // names. This script runs in the mounted harness page and catches what a
252
+ // regex cannot: a bordered box nested in a bordered box across files, tinted
253
+ // panels stacked three deep, text that computes below 12px, horizontal
254
+ // overflow at a phone width, and loading placeholders that outlive the data.
255
+ // Palette colors are deliberately not checked here: at runtime a token and a
256
+ // hardcoded hue resolve to the same RGB, so that stays with the static lint.
257
+ // ---------------------------------------------------------------------------
258
+ export const DESIGN_ASSERTIONS_MARKER = '__notisDesignAssertions';
259
+
260
+ export const DESIGN_ASSERTIONS_SCRIPT = `(() => {
261
+ /* ${DESIGN_ASSERTIONS_MARKER} */
262
+ const root = document.getElementById('root') || document.body;
263
+ const out = [];
264
+ const seen = new Set();
265
+ const push = (kind, el, extra) => {
266
+ const cls = String(el.className && el.className.baseVal !== undefined ? el.className.baseVal : el.className || '').trim().slice(0, 80);
267
+ const text = (el.textContent || '').trim().replace(/\\s+/g, ' ').slice(0, 60);
268
+ const snippet = el.tagName.toLowerCase() + (cls ? '.' + cls.split(/\\s+/).slice(0, 6).join('.') : '') + (text ? ' "' + text + '"' : '');
269
+ const key = kind + '|' + snippet;
270
+ if (seen.has(key)) return;
271
+ seen.add(key);
272
+ out.push(Object.assign({ kind, snippet }, extra || {}));
273
+ };
274
+ const controls = new Set(['INPUT', 'SELECT', 'TEXTAREA', 'BUTTON', 'A', 'SUMMARY']);
275
+ const isControl = (el) => controls.has(el.tagName) || /^(button|checkbox|switch|radio|combobox|textbox|tab|menuitem)$/.test(el.getAttribute('role') || '');
276
+ const visible = (el) => { const r = el.getBoundingClientRect(); return r.width > 0 && r.height > 0; };
277
+ const isBox = (el) => {
278
+ const cs = getComputedStyle(el);
279
+ if (!['borderTopWidth', 'borderRightWidth', 'borderBottomWidth', 'borderLeftWidth'].every((k) => parseFloat(cs[k]) > 0)) return false;
280
+ if (/rgba\\(\\d+, \\d+, \\d+, 0\\)|transparent/.test(cs.borderTopColor)) return false;
281
+ const r = el.getBoundingClientRect();
282
+ return r.width > 40 && r.height > 20;
283
+ };
284
+ const bg = (el) => getComputedStyle(el).backgroundColor;
285
+ const transparent = (c) => !c || c === 'transparent' || /rgba\\(\\d+, \\d+, \\d+, 0\\)/.test(c);
286
+ const isPanel = (el) => {
287
+ if (transparent(bg(el))) return false;
288
+ if (parseFloat(getComputedStyle(el).borderRadius) <= 0) return false;
289
+ const r = el.getBoundingClientRect();
290
+ return r.width > 160 && r.height > 48;
291
+ };
292
+ const all = Array.from(root.querySelectorAll('*'));
293
+ for (const el of all) {
294
+ if (!visible(el) || isControl(el)) continue;
295
+ if (isBox(el)) {
296
+ let p = el.parentElement; let depth = 0;
297
+ while (p && p !== root && depth < 6) { if (!isControl(p) && isBox(p)) { push('nested_border_box', el); break; } p = p.parentElement; depth += 1; }
353
298
  }
354
- await delay(250);
355
- }
356
-
357
- if (lastErrors.length > 0) {
358
- return { ok: false, mounted, screenshotPath: null, errors: lastErrors, tool_error: null };
359
- }
360
- if (!mounted) {
361
- return {
362
- ok: false,
363
- mounted: false,
364
- screenshotPath: null,
365
- errors: [],
366
- timed_out: true,
367
- tool_error: lastReadError || {
368
- phase: 'harness',
369
- message: 'Timed out waiting for window.__harness.mounted.',
370
- },
371
- };
372
- }
373
-
374
- // Strip harness chrome (debug banner, banner padding, 100vh min-height) so
375
- // the frame contains only the app, then wait for webfonts before measuring.
376
- await runAgentBrowser(
377
- ['--session', sessionName, 'eval', PREPARE_CAPTURE_SCRIPT],
378
- { timeoutMs: 5000 },
379
- );
380
- await waitForFonts(sessionName);
381
-
382
- // Fit the CSS viewport to the rendered content. Content height depends on
383
- // viewport width, so refine once after the first resize reflows the page.
384
- let framing = null;
385
- if (scaledViewport) {
386
- let contentHeight = await evalNumber(sessionName, CONTENT_HEIGHT_SCRIPT);
387
- for (let pass = 0; contentHeight != null && pass < 2; pass += 1) {
388
- const fitted = fitCssViewport(contentHeight, width, height);
389
- if (framing && fitted.width === framing.width) {
390
- break;
391
- }
392
- scaledViewport = await setViewport(sessionName, fitted.width, fitted.height, fitted.scale);
393
- if (!scaledViewport) {
394
- framing = null;
395
- await setViewport(sessionName, width, height);
396
- break;
397
- }
398
- framing = { ...fitted, content_height: contentHeight };
399
- await delay(150);
400
- const remeasured = await evalNumber(sessionName, CONTENT_HEIGHT_SCRIPT);
401
- if (remeasured == null || Math.abs(remeasured - contentHeight) <= 32) {
402
- framing.content_height = remeasured ?? contentHeight;
403
- break;
404
- }
405
- contentHeight = remeasured;
299
+ if (isPanel(el)) {
300
+ let p = el.parentElement; let panels = 0;
301
+ while (p && p !== root) { if (isPanel(p)) panels += 1; p = p.parentElement; }
302
+ if (panels >= 2) push('nested_tinted_panel', el, { depth: panels + 1 });
406
303
  }
407
304
  }
305
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
306
+ let node;
307
+ while ((node = walker.nextNode())) {
308
+ const text = (node.textContent || '').trim();
309
+ if (!text) continue;
310
+ const el = node.parentElement;
311
+ if (!el || !visible(el)) continue;
312
+ const size = parseFloat(getComputedStyle(el).fontSize);
313
+ if (size > 0 && size < 12) push('text_below_12px', el, { font_size: size });
314
+ if (/^Loading(\\.{3}|\\u2026)?(\\s|$)/.test(text)) push('loading_placeholder_after_mount', el);
315
+ }
316
+ if (root.querySelector('[data-notis-content-skeleton]')) push('loading_placeholder_after_mount', root.querySelector('[data-notis-content-skeleton]'));
317
+ const doc = document.documentElement;
318
+ if (doc.scrollWidth > doc.clientWidth + 1) out.push({ kind: 'horizontal_overflow', snippet: 'document', scroll_width: doc.scrollWidth, client_width: doc.clientWidth });
319
+ return JSON.stringify(out.slice(0, 40));
320
+ })()`;
408
321
 
409
- // Let the route settle (async data, resize transitions) before the capture.
410
- await delay(500);
322
+ export const DEFAULT_DESIGN_VIEWPORTS = [
323
+ { name: 'desktop', width: 1280, height: 900 },
324
+ { name: 'mobile', width: 390, height: 844 },
325
+ ];
411
326
 
412
- mkdirSync(dirname(screenshotPath), { recursive: true });
413
- const screenshotArgs = ['--session', sessionName, 'screenshot'];
414
- if (focusSelector) {
415
- screenshotArgs.push(focusSelector);
327
+ /**
328
+ * Evaluate the design assertions in the current page. Returns an array of
329
+ * findings, or `{ tool_error }` when agent-browser could not run the script.
330
+ */
331
+ export async function evalDesignAssertions(sessionName, { viewport = null, timeoutMs = 8000 } = {}) {
332
+ const result = await runAgentBrowser(
333
+ ['--session', sessionName, '--json', 'eval', DESIGN_ASSERTIONS_SCRIPT],
334
+ { timeoutMs },
335
+ );
336
+ if (result.exitCode !== 0) {
337
+ return { tool_error: commandError('design_eval', result) };
416
338
  }
417
- screenshotArgs.push(screenshotPath, '--screenshot-format', 'png');
418
- const shot = await runAgentBrowser(screenshotArgs, { timeoutMs: 15_000 });
419
- if (shot.exitCode !== 0) {
420
- return { ok: false, mounted: true, screenshotPath: null, errors: lastErrors, timed_out: false, tool_error: commandError('screenshot', shot) };
339
+ try {
340
+ const payload = parseAgentBrowserJson(result.stdout);
341
+ const raw = payload?.data?.result ?? payload?.result ?? payload?.data ?? null;
342
+ const value = typeof raw === 'string' ? JSON.parse(raw) : raw;
343
+ if (!Array.isArray(value)) throw new Error('Design evaluation did not return a findings array.');
344
+ return value.map((finding) => ({ ...finding, viewport: viewport || finding.viewport || null }));
345
+ } catch (error) {
346
+ return { tool_error: { phase: 'design_eval_parse', message: error.message } };
421
347
  }
348
+ }
422
349
 
423
- // The listing validator requires exact output dimensions. If the fitted
424
- // capture came out wrong (e.g. the browser ignored the scale factor),
425
- // recapture once at a plain fixed viewport.
426
- const dimensions = pngDimensions(screenshotPath);
427
- if (!focusSelector && framing && (!dimensions || dimensions.width !== width || dimensions.height !== height)) {
428
- await setViewport(sessionName, width, height);
429
- await delay(300);
430
- const retry = await runAgentBrowser(
431
- ['--session', sessionName, 'screenshot', screenshotPath, '--screenshot-format', 'png'],
432
- { timeoutMs: 15_000 },
433
- );
434
- if (retry.exitCode !== 0) {
435
- return { ok: false, mounted: true, screenshotPath: null, errors: lastErrors, timed_out: false, tool_error: commandError('screenshot', retry) };
350
+ /**
351
+ * Run every requested viewport. Missing browser checks are incomplete proof,
352
+ * not a passing design result, even when static source lint passed.
353
+ */
354
+ export async function collectDesignFindings(sessionName, { viewports = DEFAULT_DESIGN_VIEWPORTS, settleMs = 350, skipLoadingCheck = false } = {}) {
355
+ const findings = [];
356
+ let toolError = null;
357
+ for (const viewport of viewports) {
358
+ const applied = await setViewport(sessionName, viewport.width, viewport.height);
359
+ if (!applied) {
360
+ toolError ||= { phase: 'design_viewport', message: `Could not set ${viewport.name} viewport.` };
361
+ continue;
362
+ }
363
+ await delay(settleMs);
364
+ const result = await evalDesignAssertions(sessionName, { viewport: viewport.name });
365
+ if (Array.isArray(result)) {
366
+ for (const finding of result) {
367
+ if (skipLoadingCheck && finding.kind === 'loading_placeholder_after_mount') continue;
368
+ findings.push(finding);
369
+ }
370
+ } else if (!toolError) {
371
+ toolError = result.tool_error;
436
372
  }
437
- framing = null;
438
- }
439
-
440
- if (focusSelector) {
441
- framing = {
442
- ...(framing || {}),
443
- focus_selector: focusSelector,
444
- source_dimensions: dimensions,
445
- };
446
373
  }
447
-
448
- return {
449
- ok: true,
450
- mounted: true,
451
- screenshotPath,
452
- errors: lastErrors,
453
- timed_out: false,
454
- tool_error: null,
455
- framing,
456
- };
457
- }
458
-
459
- export async function closeAgentBrowserSession(sessionName) {
460
- const result = await runAgentBrowser(['--session', sessionName, 'close'], {
461
- timeoutMs: 5000,
462
- });
463
- return result.exitCode === 0;
374
+ return { findings, tool_error: toolError };
464
375
  }