@notis_ai/cli 0.2.18 → 0.2.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (395) hide show
  1. package/README.md +542 -146
  2. package/bin/check-runtime.js +15 -0
  3. package/bin/notis.js +2 -0
  4. package/config/notis_app_design_rules.json +320 -0
  5. package/config/portal-route-roots.json +45 -0
  6. package/dist/agent-hooks/notis-agent-hook.mjs +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 +271 -0
  277. package/package.json +21 -11
  278. package/skills/notis-cli/AGENT_INSTRUCTIONS.md +63 -12
  279. package/skills/notis-onboarding/BRIEF.md +14 -9
  280. package/skills/notis-query/cli.md +1 -1
  281. package/skills/notis-spaces/cli.md +540 -0
  282. package/src/cli.js +8 -8
  283. package/src/command-specs/agents.js +121 -8
  284. package/src/command-specs/auth.js +3 -3
  285. package/src/command-specs/diagnostics.js +97 -20
  286. package/src/command-specs/documents.js +158 -0
  287. package/src/command-specs/files.js +29 -0
  288. package/src/command-specs/handover.js +331 -79
  289. package/src/command-specs/helpers.js +50 -40
  290. package/src/command-specs/index.js +14 -5
  291. package/src/command-specs/meta.js +38 -9
  292. package/src/command-specs/onboarding.js +1 -1
  293. package/src/command-specs/profile.js +42 -1
  294. package/src/command-specs/skills.js +314 -9
  295. package/src/command-specs/smoke.js +16 -4
  296. package/src/command-specs/spaces.js +783 -0
  297. package/src/command-specs/tools.js +118 -21
  298. package/src/command-specs/version-history.js +20 -0
  299. package/src/command-specs/views.js +69 -0
  300. package/src/runtime/agent-browser.js +178 -267
  301. package/src/runtime/agent-setup.js +172 -13
  302. package/src/runtime/app-boundary-validator.js +338 -2
  303. package/src/runtime/app-data-literals.js +82 -0
  304. package/src/runtime/app-platform.js +750 -1570
  305. package/src/runtime/auth-recovery.js +17 -0
  306. package/src/runtime/base-skills.d.ts +11 -0
  307. package/src/runtime/base-skills.js +100 -3
  308. package/src/runtime/computer-identity.d.ts +30 -0
  309. package/src/runtime/computer-identity.js +151 -0
  310. package/src/runtime/config-lock.js +135 -0
  311. package/src/runtime/confined-space-build.js +74 -0
  312. package/src/runtime/confined-space-verify.js +49 -0
  313. package/src/runtime/delegated-context.js +5 -21
  314. package/src/runtime/native-record-files.js +47 -0
  315. package/src/runtime/oauth.js +342 -31
  316. package/src/runtime/profiles.js +19 -112
  317. package/src/runtime/retired-sdk-files.js +24 -0
  318. package/src/runtime/retry.js +150 -0
  319. package/src/runtime/skill-sync/cloud-client.ts +33 -8
  320. package/src/runtime/skill-sync/index.ts +443 -106
  321. package/src/runtime/skill-sync/local-scanner.ts +248 -32
  322. package/src/runtime/skill-sync/native-identity.ts +102 -0
  323. package/src/runtime/skill-sync/native-plan.ts +70 -0
  324. package/src/runtime/skill-sync/symlink-manager.ts +66 -16
  325. package/src/runtime/skill-sync/sync-plan.ts +34 -2
  326. package/src/runtime/skill-sync/types.ts +54 -0
  327. package/src/runtime/skill-sync/write-cloud-skill.ts +4 -4
  328. package/src/runtime/skill-sync-service.js +109 -0
  329. package/src/runtime/source-root.js +32 -0
  330. package/src/runtime/space-collection-scaffold.js +107 -0
  331. package/src/runtime/space-database-structures.js +229 -0
  332. package/src/runtime/space-fixture-broker.js +68 -0
  333. package/src/runtime/space-lock.js +337 -0
  334. package/src/runtime/space-platform.js +133 -0
  335. package/src/runtime/space-source.js +290 -0
  336. package/src/runtime/space-test-server.js +354 -0
  337. package/src/runtime/space-verification.js +94 -0
  338. package/src/runtime/space-verify-browser-worker.js +51 -0
  339. package/src/runtime/space-view-manifest.js +368 -0
  340. package/src/runtime/sync-skills.d.ts +5 -1
  341. package/src/runtime/sync-skills.js +11 -2
  342. package/src/runtime/tool-confirmation.js +138 -0
  343. package/src/runtime/transport.js +118 -24
  344. package/src/runtime/upsell.js +219 -0
  345. package/src/runtime/view-render.js +49 -0
  346. package/src/skill-sync-worker-entry.js +2 -0
  347. package/src/skill-sync-worker.js +114 -0
  348. package/src/space-harness-host.ts +122 -0
  349. package/template/.harness/index.html.tmpl +9 -352
  350. package/template/app/globals.css +29 -3
  351. package/template/app/layout.tsx +2 -6
  352. package/template/components/ui/badge.tsx +7 -4
  353. package/template/components/ui/button.tsx +1 -1
  354. package/template/components/ui/native-select.tsx +24 -0
  355. package/template/notis.config.ts +4 -36
  356. package/template/package-lock.json +1800 -1867
  357. package/template/package.json +16 -14
  358. package/template/postcss.config.mjs +1 -1
  359. package/template/tailwind.config.ts +1 -6
  360. package/template/tsconfig.json +1 -0
  361. package/template/vite.config.ts +2 -2
  362. package/dist/base-skills/notis-apps/SKILL.md +0 -613
  363. package/skills/notis-apps/cli.md +0 -285
  364. package/src/command-specs/apps.js +0 -3011
  365. package/src/runtime/app-changelog.js +0 -79
  366. package/src/runtime/app-dev-consumers.js +0 -154
  367. package/src/runtime/app-dev-host-lock.js +0 -80
  368. package/src/runtime/app-dev-process-identity.js +0 -111
  369. package/src/runtime/app-dev-roots.js +0 -284
  370. package/src/runtime/app-dev-server.js +0 -1140
  371. package/src/runtime/app-dev-sessions.js +0 -185
  372. package/src/runtime/app-registry-scaffolds.js +0 -367
  373. package/src/runtime/assets/store-screenshot-dark.png +0 -0
  374. package/src/runtime/cli-mode.generated.js +0 -5
  375. package/src/runtime/cli-mode.js +0 -34
  376. package/src/runtime/store-screenshot.js +0 -143
  377. package/template/CHANGELOG.md +0 -5
  378. package/template/app/page.tsx +0 -60
  379. package/template/components/ui/card.tsx +0 -56
  380. package/template/packages/sdk/src/components/DocumentEditor.tsx +0 -103
  381. package/template/packages/sdk/src/config.ts +0 -259
  382. package/template/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
  383. package/template/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
  384. package/template/packages/sdk/src/hooks/useDocument.ts +0 -78
  385. package/template/packages/sdk/src/hooks/useNotisNavigation.ts +0 -52
  386. package/template/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
  387. package/template/packages/sdk/src/runtime.ts +0 -439
  388. package/template/packages/sdk/src/vite.ts +0 -56
  389. package/template/packages/sdk/tsconfig.json +0 -15
  390. /package/{template/packages → dist}/sdk/src/components/MultiSelectDragOverlay.tsx +0 -0
  391. /package/{template/packages → dist}/sdk/src/components/ShortcutHints.tsx +0 -0
  392. /package/{template/packages → dist}/sdk/src/hooks/useActiveResource.ts +0 -0
  393. /package/{template/packages → dist}/sdk/src/hooks/useTool.ts +0 -0
  394. /package/{template/packages → dist}/sdk/src/hooks/useTools.ts +0 -0
  395. /package/{template/packages → dist}/sdk/src/ui.ts +0 -0
@@ -0,0 +1,213 @@
1
+ import { Readable } from 'node:stream';
2
+ import { createDeflate, createInflate, crc32, inflateSync } from 'node:zlib';
3
+ import { ViewRenderError } from './errors.js';
4
+
5
+ /**
6
+ * Stitch full-page screenshot slices into one PNG without ever holding the
7
+ * whole page as raw pixels. Chromium rasterizes a full-page capture in one go,
8
+ * so its memory grows with the page area; slices keep that peak flat.
9
+ */
10
+ const SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
11
+ const CHANNELS = { 0: 1, 2: 3, 4: 2, 6: 4 };
12
+ // Chunks that describe the image as a whole, never its pixels or its size.
13
+ const KEEP = new Set(['sRGB', 'gAMA', 'cHRM', 'iCCP', 'pHYs']);
14
+
15
+ function invalid() { return new ViewRenderError('render_failed', 'The view could not be rendered under its current access.', 500); }
16
+
17
+ export function parsePng(buffer) {
18
+ if (!Buffer.isBuffer(buffer) || buffer.length < 33 || !buffer.subarray(0, 8).equals(SIGNATURE)) throw invalid();
19
+ let offset = 8, header, ended = false;
20
+ const idat = [], ancillary = [];
21
+ while (offset + 12 <= buffer.length) {
22
+ const length = buffer.readUInt32BE(offset);
23
+ const type = buffer.toString('latin1', offset + 4, offset + 8);
24
+ const end = offset + 12 + length;
25
+ if (end > buffer.length) throw invalid();
26
+ const data = buffer.subarray(offset + 8, offset + 8 + length);
27
+ if (type === 'IHDR') {
28
+ header = { width: data.readUInt32BE(0), height: data.readUInt32BE(4), bitDepth: data[8], colorType: data[9], interlace: data[12] };
29
+ } else if (type === 'IDAT') idat.push(data);
30
+ else if (type === 'IEND') { ended = true; break; }
31
+ else if (!idat.length && KEEP.has(type)) ancillary.push(buffer.subarray(offset, end));
32
+ offset = end;
33
+ }
34
+ if (!header || !ended || !idat.length || header.bitDepth !== 8 || !(header.colorType in CHANNELS) || header.interlace !== 0) throw invalid();
35
+ return { ...header, channels: CHANNELS[header.colorType], idat: idat.length === 1 ? idat[0] : Buffer.concat(idat), ancillary };
36
+ }
37
+
38
+ function paeth(a, b, c) {
39
+ const p = a + b - c, pa = Math.abs(p - a), pb = Math.abs(p - b), pc = Math.abs(p - c);
40
+ return pa <= pb && pa <= pc ? a : pb <= pc ? b : c;
41
+ }
42
+
43
+ /** Undo one scanline's filter in place, given the previous unfiltered row (or zeros). */
44
+ function unfilterRow(filter, row, prior, bpp) {
45
+ for (let i = 0; i < row.length; i++) {
46
+ const a = i >= bpp ? row[i - bpp] : 0, b = prior ? prior[i] : 0, c = prior && i >= bpp ? prior[i - bpp] : 0;
47
+ switch (filter) {
48
+ case 0: break;
49
+ case 1: row[i] = (row[i] + a) & 0xff; break;
50
+ case 2: row[i] = (row[i] + b) & 0xff; break;
51
+ case 3: row[i] = (row[i] + ((a + b) >> 1)) & 0xff; break;
52
+ case 4: row[i] = (row[i] + paeth(a, b, c)) & 0xff; break;
53
+ default: throw invalid();
54
+ }
55
+ }
56
+ }
57
+
58
+ function rawRows(png) {
59
+ const stride = png.width * png.channels;
60
+ const raw = inflateSync(png.idat);
61
+ if (raw.length !== png.height * (stride + 1)) throw invalid();
62
+ return { raw, stride };
63
+ }
64
+
65
+ /** Decode to 8-bit RGBA; used for slices whose format differs and in tests. */
66
+ export function decodePng(buffer) {
67
+ const png = parsePng(buffer);
68
+ const { raw, stride } = rawRows(png);
69
+ const pixels = Buffer.alloc(png.width * png.height * 4);
70
+ let prior;
71
+ for (let y = 0; y < png.height; y++) {
72
+ const start = y * (stride + 1);
73
+ const row = raw.subarray(start + 1, start + 1 + stride);
74
+ unfilterRow(raw[start], row, prior, png.channels);
75
+ prior = row;
76
+ toRgba(row, png.colorType, png.width, pixels, y * png.width * 4);
77
+ }
78
+ return { width: png.width, height: png.height, pixels };
79
+ }
80
+
81
+ /** One unfiltered row of the given colour type, written as RGBA at offset. */
82
+ function toRgba(row, colorType, width, out, offset) {
83
+ const channels = CHANNELS[colorType];
84
+ for (let x = 0; x < width; x++) {
85
+ const source = x * channels, target = offset + x * 4;
86
+ if (colorType === 6) row.copy(out, target, source, source + 4);
87
+ else if (colorType === 2) { row.copy(out, target, source, source + 3); out[target + 3] = 255; }
88
+ else if (colorType === 4) { out.fill(row[source], target, target + 3); out[target + 3] = row[source + 1]; }
89
+ else { out.fill(row[source], target, target + 3); out[target + 3] = 255; }
90
+ }
91
+ }
92
+
93
+ function chunk(type, data) {
94
+ const head = Buffer.alloc(8);
95
+ head.writeUInt32BE(data.length, 0); head.write(type, 4, 'latin1');
96
+ const tail = Buffer.alloc(4);
97
+ tail.writeUInt32BE(crc32(data, crc32(head.subarray(4))) >>> 0, 0);
98
+ return Buffer.concat([head, data, tail]);
99
+ }
100
+
101
+ /**
102
+ * Rows of one slice in the output's format, each with its filter byte. Only the
103
+ * first row changes: its filter read a zero row above it, so it is unfiltered.
104
+ */
105
+ function sliceRows(png, target) {
106
+ if (png.colorType === target.colorType) {
107
+ const { raw, stride } = rawRows(png);
108
+ const first = raw.subarray(1, 1 + stride);
109
+ unfilterRow(raw[0], first, undefined, png.channels);
110
+ raw[0] = 0;
111
+ return raw;
112
+ }
113
+ const decoded = decodePng(Buffer.concat([SIGNATURE, chunk('IHDR', ihdr(png)), chunk('IDAT', png.idat), chunk('IEND', Buffer.alloc(0))]));
114
+ const out = Buffer.alloc(png.height * (png.width * 4 + 1));
115
+ for (let y = 0; y < png.height; y++) decoded.pixels.copy(out, y * (png.width * 4 + 1) + 1, y * png.width * 4, (y + 1) * png.width * 4);
116
+ return out;
117
+ }
118
+
119
+ function ihdr({ width, height, colorType }) {
120
+ const data = Buffer.alloc(13);
121
+ data.writeUInt32BE(width, 0); data.writeUInt32BE(height, 4);
122
+ data[8] = 8; data[9] = colorType; // compression, filter and interlace stay 0
123
+ return data;
124
+ }
125
+
126
+ /**
127
+ * Stitch PNG slices, top to bottom, of one equal width, as they arrive: each
128
+ * slice is compressed into the output when it is added and is not kept, so the
129
+ * whole page is never held as pixels. Slices that share the first one's colour
130
+ * type keep it; any mix is written as RGBA. Stitching adds no size limit of its
131
+ * own: the output is refused once its compressed bytes pass maxBytes, by the add
132
+ * that produces them or, for bytes the compressor still holds, by finish.
133
+ */
134
+ export function createPngStitcher({ maxBytes = Infinity, level = 6 } = {}) {
135
+ const tooLarge = () => new ViewRenderError('render_too_large', 'The PNG exceeds the render transport limit; nothing was cropped.', 413);
136
+ // overhead: every byte of the PNG besides the compressed pixels, so the limit is on the whole file.
137
+ let first, colorType, height = 0, out, overhead = 0;
138
+ const over = () => out.size + overhead > maxBytes;
139
+ function encoder() {
140
+ const state = { compressed: [], size: 0, crc: crc32('IDAT'), deflate: createDeflate({ level }) };
141
+ state.deflate.on('data', data => { state.size += data.length; state.crc = crc32(data, state.crc); state.compressed.push(data); });
142
+ state.finished = new Promise((resolve, reject) => { state.deflate.once('end', resolve); state.deflate.once('error', reject); });
143
+ state.finished.catch(() => {});
144
+ return state;
145
+ }
146
+ async function write(rows) {
147
+ await new Promise((resolve, reject) => out.deflate.write(rows, error => (error ? reject(error) : resolve())));
148
+ if (over()) { out.deflate.destroy(); throw tooLarge(); }
149
+ }
150
+ /** Re-encode what is written so far as RGBA, one row at a time, never the whole page. */
151
+ async function upgradeToRgba() {
152
+ const old = out;
153
+ old.deflate.end();
154
+ await old.finished;
155
+ out = encoder();
156
+ const stride = first.width * CHANNELS[colorType];
157
+ let carry = Buffer.alloc(0), prior;
158
+ for await (const data of Readable.from(old.compressed).pipe(createInflate())) {
159
+ carry = carry.length ? Buffer.concat([carry, data]) : data;
160
+ const rows = Math.floor(carry.length / (stride + 1));
161
+ if (!rows) continue;
162
+ const batch = Buffer.alloc(rows * (first.width * 4 + 1));
163
+ for (let y = 0; y < rows; y++) {
164
+ const row = Buffer.from(carry.subarray(y * (stride + 1) + 1, (y + 1) * (stride + 1)));
165
+ unfilterRow(carry[y * (stride + 1)], row, prior, CHANNELS[colorType]);
166
+ toRgba(row, colorType, first.width, batch, y * (first.width * 4 + 1) + 1);
167
+ prior = row;
168
+ }
169
+ carry = carry.subarray(rows * (stride + 1));
170
+ await write(batch);
171
+ }
172
+ if (carry.length) throw invalid();
173
+ old.compressed.length = 0;
174
+ colorType = 6;
175
+ }
176
+ return {
177
+ async add(slice) {
178
+ const png = parsePng(slice);
179
+ if (png.height < 1 || (first && png.width !== first.width)) throw invalid();
180
+ if (!first) {
181
+ // Keep only the width and copies of the kept chunks, never the first slice itself.
182
+ first = { width: png.width, ancillary: png.ancillary.map(part => Buffer.from(part)) };
183
+ colorType = png.colorType; out = encoder();
184
+ // Signature, IHDR, the kept chunks, the IDAT head and CRC, and IEND.
185
+ overhead = 8 + 25 + first.ancillary.reduce((total, part) => total + part.length, 0) + 12 + 12;
186
+ }
187
+ else if (png.colorType !== colorType && colorType !== 6) await upgradeToRgba();
188
+ // Raw rows of one slice at a time: never the whole page.
189
+ await write(sliceRows(png, { colorType }));
190
+ height += png.height;
191
+ },
192
+ async finish() {
193
+ if (!first) throw invalid();
194
+ out.deflate.end();
195
+ await out.finished;
196
+ if (over()) throw tooLarge();
197
+ const head = Buffer.alloc(8), tail = Buffer.alloc(4);
198
+ head.writeUInt32BE(out.size, 0); head.write('IDAT', 4, 'latin1'); tail.writeUInt32BE(out.crc >>> 0, 0);
199
+ const png = Buffer.concat([SIGNATURE, chunk('IHDR', ihdr({ width: first.width, height, colorType })), ...first.ancillary,
200
+ head, ...out.compressed, tail, chunk('IEND', Buffer.alloc(0))]);
201
+ if (png.length !== out.size + overhead) throw invalid();
202
+ return png;
203
+ },
204
+ };
205
+ }
206
+
207
+ /** Stitch every slice at once; the same output as adding them one by one. */
208
+ export async function stitchPng(slices, options) {
209
+ if (!Array.isArray(slices) || !slices.length) throw invalid();
210
+ const stitcher = createPngStitcher(options);
211
+ for (const slice of slices) await stitcher.add(slice);
212
+ return stitcher.finish();
213
+ }
@@ -0,0 +1,110 @@
1
+ import { ViewRenderError } from './errors.js';
2
+
3
+ export function browserEnvironment(environment = process.env) {
4
+ const names = ['PATH', 'HOME', 'TMPDIR', 'TEMP', 'TMP', 'LANG', 'SystemRoot', 'WINDIR', 'DISPLAY', 'XDG_RUNTIME_DIR'];
5
+ return Object.fromEntries(names.filter(name => typeof environment[name] === 'string').map(name => [name, environment[name]]));
6
+ }
7
+
8
+ /**
9
+ * A warm process, never a shared profile, cookie jar, localStorage or context.
10
+ * recycleAfter closes the browser once it has served that many renders and
11
+ * nothing is running or waiting, so a small host returns to its idle memory;
12
+ * the next render starts a fresh browser.
13
+ */
14
+ export function createBrowserPool({ concurrency = 2, maxQueue = 8, recycleAfter = 0, launch = {}, chromium } = {}) {
15
+ if (!Number.isInteger(concurrency) || concurrency < 1 || concurrency > 4 || !Number.isInteger(maxQueue) || maxQueue < 0 || maxQueue > 32
16
+ || !Number.isInteger(recycleAfter) || recycleAfter < 0 || recycleAfter > 10000) throw new TypeError('Choose a bounded render pool.');
17
+ let browser, starting, retiring, closing = false, inUse = 0, served = 0, renders = 0;
18
+ const queue = new Set();
19
+ const contexts = new Set();
20
+ async function acquire(signal) {
21
+ if (closing || signal?.aborted) throw new ViewRenderError('render_cancelled', 'The render was cancelled.', 499);
22
+ if (inUse < concurrency) { inUse++; return; }
23
+ if (queue.size >= maxQueue) throw new ViewRenderError('render_busy', 'The renderer is busy. Try again shortly.', 503);
24
+ await new Promise((resolve, reject) => {
25
+ const entry = { resolve: () => { signal?.removeEventListener('abort', abort); resolve(); }, reject };
26
+ const abort = () => { queue.delete(entry); reject(new ViewRenderError('render_cancelled', 'The render was cancelled.', 499)); };
27
+ queue.add(entry); signal?.addEventListener('abort', abort, { once: true });
28
+ });
29
+ }
30
+ function release() {
31
+ const next = queue.values().next().value;
32
+ if (next) { queue.delete(next); next.resolve(); return; }
33
+ inUse--;
34
+ if (recycleAfter && served >= recycleAfter) retire();
35
+ }
36
+ function retire() {
37
+ if (inUse !== 0 || !browser || closing) return;
38
+ const old = browser;
39
+ browser = undefined; served = 0;
40
+ const done = old.close().catch(() => {}).then(() => { if (retiring === done) retiring = undefined; });
41
+ retiring = done;
42
+ }
43
+ async function getBrowser() {
44
+ if (browser?.isConnected()) return browser;
45
+ if (!starting) starting = (async () => {
46
+ // A retired browser has released its memory before the next one starts.
47
+ if (retiring) await retiring;
48
+ // Importing CLI command metadata never loads Playwright or starts Chrome.
49
+ const engine = chromium || (await import('playwright-core')).chromium;
50
+ const allowed = Object.fromEntries(['executablePath', 'channel', 'timeout'].filter(key => key in launch).map(key => [key, launch[key]]));
51
+ try {
52
+ browser = await engine.launch({ ...allowed, headless: true, chromiumSandbox: true,
53
+ env: browserEnvironment(), args: ['--force-webrtc-ip-handling-policy=disable_non_proxied_udp', '--disable-background-networking',
54
+ // A container's small /dev/shm cannot hold a tall page's shared memory.
55
+ '--disable-dev-shm-usage'] });
56
+ } catch (error) {
57
+ if (/Executable doesn't exist/.test(String(error?.message))) throw new ViewRenderError('render_browser_missing', 'Install the pinned Chromium browser with npx playwright-core@1.63.0 install chromium.', 503);
58
+ throw new ViewRenderError('render_browser_unavailable', 'Sandboxed Chromium could not start.', 503);
59
+ }
60
+ return browser;
61
+ })().finally(() => { starting = undefined; });
62
+ return starting;
63
+ }
64
+ return {
65
+ warm: () => getBrowser(),
66
+ /**
67
+ * Prove Chromium can start, failing like a render would when it cannot. A
68
+ * recycling pool closes the browser again at once, so an idle host keeps
69
+ * no browser; otherwise it stays warm for the first render.
70
+ */
71
+ async check() {
72
+ await getBrowser();
73
+ if (recycleAfter) retire();
74
+ if (retiring) await retiring;
75
+ },
76
+ async use(operation, { signal, width = 1440, locale = 'en', timezone = 'UTC' } = {}) {
77
+ await acquire(signal);
78
+ let context, launched = false;
79
+ const abort = () => { void context?.close(); };
80
+ try {
81
+ const browser = await getBrowser();
82
+ launched = true;
83
+ if (closing || signal?.aborted) throw new ViewRenderError('render_cancelled', 'The render was cancelled.', 499);
84
+ context = await browser.newContext({ viewport: { width, height: 900 }, deviceScaleFactor: 1,
85
+ locale, timezoneId: timezone, colorScheme: 'light', serviceWorkers: 'block', acceptDownloads: false, permissions: [] });
86
+ contexts.add(context); signal?.addEventListener('abort', abort, { once: true });
87
+ if (signal?.aborted) throw new ViewRenderError('render_cancelled', 'The render was cancelled.', 499);
88
+ return await operation(context);
89
+ } finally {
90
+ signal?.removeEventListener('abort', abort);
91
+ if (context) { contexts.delete(context); served++; renders++; await context.close().catch(() => {}); }
92
+ // A browser that failed to open a context still counts, so recycling closes it too.
93
+ else if (launched) served++;
94
+ release();
95
+ }
96
+ },
97
+ async close() {
98
+ closing = true;
99
+ for (const entry of queue) entry.reject(new ViewRenderError('render_cancelled', 'The renderer is stopping.', 503));
100
+ queue.clear();
101
+ await Promise.allSettled([...contexts].map(context => context.close()));
102
+ if (starting) await starting.catch(() => {});
103
+ await browser?.close();
104
+ if (retiring) await retiring;
105
+ },
106
+ // chromium: a browser may hold memory, from its launch until its close has finished.
107
+ stats: () => ({ active: inUse, queued: queue.size, contexts: contexts.size, warm: Boolean(browser?.isConnected()),
108
+ chromium: Boolean(starting || browser || retiring), renders }),
109
+ };
110
+ }
@@ -0,0 +1,59 @@
1
+ import { ViewRenderError } from './errors.js';
2
+
3
+ /** The purpose credential never enters request URLs, browser headers or source. */
4
+ export function createRemoteRenderBroker({ apiBase, credential, fetcher = fetch }) {
5
+ const base = new URL(apiBase);
6
+ if (!['https:', 'http:'].includes(base.protocol) || (base.protocol === 'http:' && !['127.0.0.1', 'localhost', '[::1]'].includes(base.hostname))
7
+ || base.username || base.password || base.search || base.hash || typeof credential !== 'string' || !credential) throw new TypeError('Use a configured render API and its purpose credential.');
8
+ let claims;
9
+ try { claims = JSON.parse(Buffer.from(credential.split('.')[1], 'base64url').toString('utf8')); } catch {}
10
+ // This is rejection/identity checking only. Python performs cryptographic
11
+ // authentication and live authorization before returning any data.
12
+ if (claims?.type !== 'notis_view_render' || claims?.aud !== 'notis-view-render' || claims?.read_only !== true
13
+ || !claims?.jti || !claims?.target || typeof claims.url !== 'string') throw new ViewRenderError('render_credential_invalid', 'Use a dedicated read-only render credential, never a user JWT.', 401);
14
+ if (!Array.isArray(claims.outputs) || !claims.outputs.length || claims.outputs.some(value => !['markdown', 'screenshot'].includes(value))
15
+ || new Set(claims.outputs).size !== claims.outputs.length || ![390, 1440].includes(claims.width)) {
16
+ throw new ViewRenderError('render_credential_invalid', 'This credential does not pin render outputs.', 401);
17
+ }
18
+ const stable = value => JSON.stringify(value, (_key, item) => item && typeof item === 'object' && !Array.isArray(item)
19
+ ? Object.fromEntries(Object.keys(item).sort().map(key => [key, item[key]])) : item);
20
+ const endpoint = path => new URL(`portal_view_render/${path}`, base.href.replace(/\/?$/, '/')).href;
21
+ async function request(operation, body, { signal } = {}) {
22
+ const response = await fetcher(endpoint(operation), { method: 'POST', redirect: 'error', credentials: 'omit', signal,
23
+ headers: { Authorization: `Bearer ${credential}`, 'Content-Type': 'application/json', 'X-Notis-Spaces-Protocol': '2' },
24
+ body: JSON.stringify(body) });
25
+ let result;
26
+ try { result = await response.json(); } catch { throw new ViewRenderError('render_broker_unavailable', 'The render read could not be confirmed.', 503); }
27
+ if (!response.ok) throw new ViewRenderError(typeof result?.error === 'string' ? result.error : 'render_unavailable', 'This view is unavailable for rendering.', response.status);
28
+ return result;
29
+ }
30
+ return Object.freeze({
31
+ snapshot: async options => {
32
+ const value = await request('read', { operation: 'manifest' }, options);
33
+ if (value?.url !== claims.url || value?.view?.space_id !== claims.target.space_id || value.view.revision !== claims.target.revision
34
+ || (value.record_key || null) !== (claims.target.record_key || null) || stable(value.params) !== stable(claims.target.params)
35
+ || value.descriptor?.space?.id !== claims.target.space_id || value.descriptor.space.revision !== claims.target.revision
36
+ || value.descriptor?.resource?.id !== claims.target.space_id || value.descriptor.resource.revision !== claims.target.revision
37
+ || stable(value.render_options) !== stable({ outputs: claims.outputs, width: claims.width })) {
38
+ throw new ViewRenderError('render_manifest_invalid', 'The render manifest belongs to another view.', 502);
39
+ }
40
+ return value;
41
+ },
42
+ read: (payload, options) => request('read', payload, options),
43
+ finish: async options => {
44
+ const value = await request('finish', {}, options);
45
+ if (value?.valid !== true || value.render_id !== claims.jti || value.url !== claims.url) {
46
+ throw new ViewRenderError('render_access_changed', 'The final render authority is not confirmed.', 403);
47
+ }
48
+ return value;
49
+ },
50
+ complete: async (rendered, options) => {
51
+ const value = await request('complete', { rendered }, options);
52
+ if (value?.valid !== true || value.render_id !== claims.jti || value.url !== claims.url) {
53
+ throw new ViewRenderError('render_capture_unconfirmed', 'The completed capture could not be recorded.', 409);
54
+ }
55
+ // Attestation/receipts remain backend-only even if an endpoint regresses.
56
+ return { valid: true, render_id: value.render_id, url: value.url };
57
+ },
58
+ });
59
+ }