akanjs 3.0.2 → 3.1.0-rc.1

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/CHANGELOG.md +26 -0
  2. package/base/baseEnv.ts +0 -4
  3. package/base/primitiveRegistry.ts +2 -2
  4. package/client/cookie.ts +5 -1
  5. package/client/csrLocation.ts +122 -0
  6. package/client/csrTypes.ts +36 -4
  7. package/client/device.ts +2 -1
  8. package/client/frameConfig.ts +78 -7
  9. package/client/index.ts +3 -0
  10. package/client/makePageProto.tsx +3 -2
  11. package/client/native/external-view.ts +1 -0
  12. package/client/route/RootLayoutDefinition.ts +7 -1
  13. package/client/route/RouteHead.ts +108 -0
  14. package/client/route/index.ts +1 -0
  15. package/client/router.ts +36 -23
  16. package/client/rscNavigation.ts +8 -2
  17. package/client/viewportMeta.ts +60 -0
  18. package/client/webManifest.ts +18 -0
  19. package/common/Logger.ts +7 -2
  20. package/common/PipelineNames.ts +31 -0
  21. package/common/PipelinePattern.ts +70 -0
  22. package/common/PipelinePlanSchema.ts +490 -0
  23. package/common/backoff.ts +14 -0
  24. package/common/index.ts +39 -0
  25. package/common/pipelinePlan.ts +259 -0
  26. package/common/routeConvention.ts +1 -0
  27. package/common/types.ts +17 -0
  28. package/constant/constantRegistry.ts +26 -6
  29. package/dictionary/base.dictionary.ts +12 -0
  30. package/dictionary/trans.ts +9 -1
  31. package/document/into.ts +5 -0
  32. package/fetch/client/fetchClient.ts +135 -31
  33. package/fetch/client/httpClient.ts +45 -0
  34. package/fetch/client/wsClient.ts +125 -16
  35. package/fetch/fetchType/buildFetch.type.ts +1 -0
  36. package/index.ts +243 -10
  37. package/package.json +1 -5
  38. package/server/akanApp.ts +5 -2
  39. package/server/akanServer.ts +44 -8
  40. package/server/artifact/builderRpc.ts +8 -3
  41. package/server/artifact/ipcTypes.ts +5 -2
  42. package/server/di/diLifecycle.ts +39 -3
  43. package/server/di/utils.ts +28 -1
  44. package/server/ed25519Key.ts +48 -0
  45. package/server/index.ts +1 -0
  46. package/server/machine/index.ts +2 -0
  47. package/server/machine/machineAccount.ts +27 -0
  48. package/server/machine/machineToken.ts +151 -0
  49. package/server/mcp/McpApps.ts +256 -0
  50. package/server/mcp/McpDispatcher.ts +31 -7
  51. package/server/mcp/McpRouter.ts +209 -46
  52. package/server/ops/opsToken.ts +3 -10
  53. package/server/resolver/database.resolver.ts +3 -0
  54. package/server/resolver/signal.resolver.ts +40 -3
  55. package/server/routeElementComposer.tsx +78 -2
  56. package/server/routeState.ts +6 -0
  57. package/server/routeTreeBuilder.ts +26 -9
  58. package/server/routing/apiRouter.ts +6 -3
  59. package/server/routing/roomReplay.ts +88 -0
  60. package/server/rscClient.tsx +53 -8
  61. package/server/rscWorker.tsx +141 -27
  62. package/server/rscWorkerHost.ts +18 -5
  63. package/server/sitemap.ts +14 -0
  64. package/server/ssrFromRscRenderer.tsx +12 -6
  65. package/server/ssrTypes.ts +3 -1
  66. package/server/systemPageDocument.tsx +38 -30
  67. package/server/types.tsx +2 -0
  68. package/server/webRouter.ts +74 -5
  69. package/service/adapt.ts +1 -1
  70. package/service/injectInfo.ts +9 -1
  71. package/service/ipcTypes.ts +2 -1
  72. package/service/predefinedAdaptor/database.adaptor.ts +1 -146
  73. package/service/predefinedAdaptor/queue.adaptor.ts +44 -6
  74. package/service/predefinedAdaptor/queueBackoff.ts +12 -0
  75. package/service/predefinedAdaptor/solidQueue.adaptor.ts +17 -13
  76. package/service/predefinedAdaptor/sql/SqlDocumentStore.ts +44 -2
  77. package/service/predefinedAdaptor/sql/dialect/postgres.ts +3 -0
  78. package/service/predefinedAdaptor/sql/dialect/sqlite.ts +5 -2
  79. package/service/predefinedAdaptor/sql/search/fts5.ts +1 -1
  80. package/service/predefinedAdaptor/sql/types.ts +8 -6
  81. package/service/predefinedAdaptor/sql/values.ts +0 -23
  82. package/service/serve.ts +5 -1
  83. package/signal/agent.signal.ts +1 -1
  84. package/signal/base.signal.ts +2 -2
  85. package/signal/endpointIdempotency.ts +53 -0
  86. package/signal/internalArg.ts +14 -0
  87. package/signal/mcp/McpDocument.ts +38 -1
  88. package/signal/mcp/mcpProtocol.ts +18 -0
  89. package/signal/signalContext.ts +58 -30
  90. package/signal/signalRegistry.ts +14 -4
  91. package/signal/types.ts +18 -3
  92. package/store/action.ts +17 -19
  93. package/store/agent/StoreSurfaceSource.ts +1 -1
  94. package/store/agentic/StToolBuilder.ts +3 -0
  95. package/test/conformance.ts +9 -33
  96. package/types/base/baseEnv.d.ts +0 -4
  97. package/types/base/primitiveRegistry.d.ts +1 -1
  98. package/types/client/csrLocation.d.ts +32 -0
  99. package/types/client/csrTypes.d.ts +43 -4
  100. package/types/client/frameConfig.d.ts +14 -2
  101. package/types/client/index.d.ts +3 -0
  102. package/types/client/native/external-view.d.ts +1 -0
  103. package/types/client/route/RootLayoutDefinition.d.ts +2 -1
  104. package/types/client/route/RouteHead.d.ts +13 -0
  105. package/types/client/route/index.d.ts +1 -0
  106. package/types/client/router.d.ts +6 -0
  107. package/types/client/rscNavigation.d.ts +4 -0
  108. package/types/client/viewportMeta.d.ts +18 -0
  109. package/types/client/webManifest.d.ts +4 -0
  110. package/types/common/PipelineNames.d.ts +6 -0
  111. package/types/common/PipelinePattern.d.ts +14 -0
  112. package/types/common/PipelinePlanSchema.d.ts +9 -0
  113. package/types/common/backoff.d.ts +10 -0
  114. package/types/common/index.d.ts +5 -0
  115. package/types/common/pipelinePlan.d.ts +224 -0
  116. package/types/common/types.d.ts +16 -0
  117. package/types/constant/constantRegistry.d.ts +5 -2
  118. package/types/dictionary/base.dictionary.d.ts +1 -1
  119. package/types/dictionary/dictionary.d.ts +8 -8
  120. package/types/document/into.d.ts +9 -0
  121. package/types/fetch/client/fetchClient.d.ts +31 -4
  122. package/types/fetch/client/httpClient.d.ts +8 -0
  123. package/types/fetch/client/wsClient.d.ts +26 -2
  124. package/types/fetch/fetchType/buildFetch.type.d.ts +1 -0
  125. package/types/index.d.ts +230 -9
  126. package/types/server/akanServer.d.ts +16 -1
  127. package/types/server/artifact/builderRpc.d.ts +7 -2
  128. package/types/server/artifact/ipcTypes.d.ts +5 -1
  129. package/types/server/di/utils.d.ts +1 -0
  130. package/types/server/ed25519Key.d.ts +13 -0
  131. package/types/server/index.d.ts +1 -0
  132. package/types/server/machine/index.d.ts +2 -0
  133. package/types/server/machine/machineAccount.d.ts +5 -0
  134. package/types/server/machine/machineToken.d.ts +29 -0
  135. package/types/server/machine.d.ts +1 -0
  136. package/types/server/mcp/McpApps.d.ts +50 -0
  137. package/types/server/mcp/McpDispatcher.d.ts +4 -1
  138. package/types/server/mcp/McpRouter.d.ts +4 -0
  139. package/types/server/resolver/signal.resolver.d.ts +2 -1
  140. package/types/server/routeElementComposer.d.ts +24 -4
  141. package/types/server/routeState.d.ts +4 -1
  142. package/types/server/routing/roomReplay.d.ts +24 -0
  143. package/types/server/rscWorkerHost.d.ts +5 -2
  144. package/types/server/sitemap.d.ts +2 -0
  145. package/types/server/ssrFromRscRenderer.d.ts +3 -3
  146. package/types/server/ssrTypes.d.ts +4 -1
  147. package/types/server/systemPageDocument.d.ts +2 -0
  148. package/types/server/types.d.ts +4 -1
  149. package/types/server/webRouter.d.ts +3 -1
  150. package/types/service/adapt.d.ts +1 -1
  151. package/types/service/ipcTypes.d.ts +2 -0
  152. package/types/service/predefinedAdaptor/database.adaptor.d.ts +1 -24
  153. package/types/service/predefinedAdaptor/queue.adaptor.d.ts +10 -3
  154. package/types/service/predefinedAdaptor/queueBackoff.d.ts +4 -0
  155. package/types/service/predefinedAdaptor/solidQueue.adaptor.d.ts +3 -3
  156. package/types/service/predefinedAdaptor/sql/SqlDocumentStore.d.ts +6 -1
  157. package/types/service/predefinedAdaptor/sql/dialect/postgres.d.ts +1 -0
  158. package/types/service/predefinedAdaptor/sql/dialect/sqlite.d.ts +1 -0
  159. package/types/service/predefinedAdaptor/sql/search/fts5.d.ts +1 -1
  160. package/types/service/predefinedAdaptor/sql/types.d.ts +9 -5
  161. package/types/service/predefinedAdaptor/sql/values.d.ts +0 -3
  162. package/types/service/serve.d.ts +3 -0
  163. package/types/signal/endpointIdempotency.d.ts +7 -0
  164. package/types/signal/internalArg.d.ts +10 -0
  165. package/types/signal/mcp/McpDocument.d.ts +8 -0
  166. package/types/signal/mcp/mcpProtocol.d.ts +17 -0
  167. package/types/signal/signalContext.d.ts +5 -3
  168. package/types/signal/signalRegistry.d.ts +4 -4
  169. package/types/signal/types.d.ts +21 -1
  170. package/types/store/agentic/StToolBuilder.d.ts +2 -0
  171. package/types/test/conformance.d.ts +7 -10
  172. package/types/ui/Agent/Chat.d.ts +5 -1
  173. package/types/ui/Agent/EmbedApproval.d.ts +1 -0
  174. package/types/ui/Agent/index.d.ts +3 -0
  175. package/types/ui/Agent/useKeyboardFrame.d.ts +2 -0
  176. package/types/ui/Agent/useScreenActions.d.ts +18 -0
  177. package/types/ui/BottomSheet.d.ts +1 -0
  178. package/types/ui/Constant/Doc.d.ts +11 -17
  179. package/types/ui/Constant/index.d.ts +7 -2
  180. package/types/ui/Constant/schemaDoc.d.ts +11 -0
  181. package/types/ui/Dialog/Modal.d.ts +6 -1
  182. package/types/ui/Dialog/index.d.ts +1 -1
  183. package/types/ui/Dialog/modalSurface.d.ts +12 -0
  184. package/types/ui/Layout/BottomInset.d.ts +1 -1
  185. package/types/ui/Layout/BottomTab.d.ts +11 -2
  186. package/types/ui/Layout/FrameSlotFallback.d.ts +9 -0
  187. package/types/ui/Layout/Navbar.d.ts +4 -1
  188. package/types/ui/Layout/TopInset.d.ts +1 -1
  189. package/types/ui/Layout/index.d.ts +4 -4
  190. package/types/ui/Link/Action.d.ts +5 -1
  191. package/types/ui/Link/Anchor.d.ts +5 -3
  192. package/types/ui/Link/activePath.d.ts +13 -0
  193. package/types/ui/Link/index.d.ts +1 -1
  194. package/types/ui/Modal.d.ts +6 -1
  195. package/types/ui/Model/EditModal.d.ts +6 -1
  196. package/types/ui/Model/New.d.ts +6 -1
  197. package/types/ui/Model/ViewEditModal.d.ts +6 -1
  198. package/types/ui/Model/ViewModal.d.ts +6 -1
  199. package/types/ui/Model/newFormOrigin.d.ts +7 -0
  200. package/types/ui/Portal.d.ts +3 -1
  201. package/types/ui/Reference/origin.d.ts +12 -0
  202. package/types/ui/Signal/Doc.d.ts +23 -18
  203. package/types/ui/Signal/Listener.d.ts +2 -6
  204. package/types/ui/Signal/Object.d.ts +6 -10
  205. package/types/ui/Signal/Response.d.ts +3 -6
  206. package/types/ui/Signal/RestApi.d.ts +11 -15
  207. package/types/ui/Signal/WebSocket.d.ts +11 -21
  208. package/types/ui/Signal/endpointEntries.d.ts +8 -0
  209. package/types/ui/Signal/index.d.ts +35 -12
  210. package/types/ui/Signal/signalText.d.ts +178 -0
  211. package/types/ui/System/CSR.d.ts +2 -2
  212. package/types/ui/System/Client.d.ts +4 -3
  213. package/types/ui/System/Common.d.ts +27 -6
  214. package/types/ui/System/SSR.d.ts +6 -6
  215. package/types/ui/Tooltip.d.ts +4 -2
  216. package/types/ui/index.d.ts +2 -1
  217. package/types/ui/overlayLayer.d.ts +6 -0
  218. package/types/ui/overlayPosition.d.ts +17 -0
  219. package/types/vendor/@akanjs/native/packages/cli/src/api.d.ts +14 -3
  220. package/types/vendor/@akanjs/native/packages/cli/src/commands/update.d.ts +1 -1
  221. package/types/vendor/@akanjs/native/packages/cli/src/config.d.ts +30 -5
  222. package/types/vendor/@akanjs/native/packages/cli/src/lib/compat.d.ts +8 -0
  223. package/types/vendor/@akanjs/native/packages/cli/src/lib/desktop-server.d.ts +5 -0
  224. package/types/vendor/@akanjs/native/packages/cli/src/lib/distribution.d.ts +22 -0
  225. package/types/vendor/@akanjs/native/packages/cli/src/lib/project.d.ts +4 -0
  226. package/types/vendor/@akanjs/native/packages/cli/src/lib/publish.d.ts +16 -10
  227. package/types/vendor/@akanjs/native/packages/cli/src/lib/updates.d.ts +51 -7
  228. package/types/vendor/@akanjs/native/packages/cli/src/lib/vectors.d.ts +1 -1
  229. package/types/vendor/@akanjs/native/packages/cli/src/platforms/windows-installer.d.ts +12 -4
  230. package/types/vendor/@akanjs/native/packages/cli/src/platforms/windows.d.ts +5 -0
  231. package/types/vendor/@akanjs/native/packages/core/src/kernel.d.ts +36 -0
  232. package/types/vendor/@akanjs/native/packages/desktop/src/dispatcher.d.ts +6 -0
  233. package/types/vendor/@akanjs/native/packages/desktop/src/plugin.d.ts +2 -0
  234. package/types/vendor/@akanjs/native/packages/desktop/src/server.d.ts +11 -0
  235. package/types/vendor/@akanjs/native/plugins/external-view/src/desktop.d.ts +17 -0
  236. package/types/vendor/@akanjs/native/plugins/external-view/src/index.d.ts +132 -0
  237. package/types/vendor/@akanjs/native/plugins/external-view/src.d.ts +1 -0
  238. package/types/vendor/@akanjs/native/plugins/single-instance/src/socket.d.ts +1 -1
  239. package/types/vendor/@akanjs/native/plugins/updates/src/index.d.ts +8 -0
  240. package/types/vendor/@akanjs/native/plugins/window/src/desktop.d.ts +4 -1
  241. package/types/vendor/@akanjs/native/plugins/window/src/index.d.ts +49 -0
  242. package/types/vendor/use-agentic/AgentActivity.d.ts +3 -1
  243. package/types/vendor/use-agentic/AgenticSurface.d.ts +2 -2
  244. package/types/vendor/use-agentic/types.d.ts +2 -0
  245. package/types/webkit/CsrStack.d.ts +1 -1
  246. package/types/webkit/EmbeddedLayout.d.ts +10 -0
  247. package/types/webkit/McpAppAgent.d.ts +14 -0
  248. package/types/webkit/McpAppApprovals.d.ts +8 -0
  249. package/types/webkit/McpAppBridge.d.ts +101 -0
  250. package/types/webkit/McpAppView.d.ts +21 -0
  251. package/types/webkit/RenderLayer.d.ts +3 -1
  252. package/types/webkit/index.d.ts +1 -0
  253. package/types/webkit/useLocation.d.ts +1 -1
  254. package/types/webkit/useVisualViewportFrame.d.ts +13 -0
  255. package/ui/Agent/Chat.tsx +29 -10
  256. package/ui/Agent/Composer.tsx +2 -1
  257. package/ui/Agent/EmbedApproval.tsx +17 -0
  258. package/ui/Agent/RichInput.tsx +1 -1
  259. package/ui/Agent/index.ts +2 -0
  260. package/ui/Agent/useKeyboardFrame.ts +10 -0
  261. package/ui/Agent/useScreenActions.ts +45 -0
  262. package/ui/BottomSheet.tsx +82 -53
  263. package/ui/Constant/Doc.tsx +113 -60
  264. package/ui/Constant/index.ts +2 -2
  265. package/ui/Constant/schemaDoc.ts +86 -26
  266. package/ui/Dialog/LegacyModal.tsx +53 -52
  267. package/ui/Dialog/Modal.tsx +93 -49
  268. package/ui/Dialog/modalSurface.ts +61 -0
  269. package/ui/DragAction.tsx +1 -1
  270. package/ui/DraggableList.tsx +1 -1
  271. package/ui/Dropdown.tsx +4 -6
  272. package/ui/KeyboardAvoiding.tsx +1 -1
  273. package/ui/Layout/BottomInset.tsx +32 -16
  274. package/ui/Layout/BottomTab.tsx +42 -16
  275. package/ui/Layout/FrameSlotFallback.tsx +59 -0
  276. package/ui/Layout/Navbar.tsx +56 -15
  277. package/ui/Layout/Sider.tsx +1 -1
  278. package/ui/Layout/TopInset.tsx +22 -17
  279. package/ui/Link/Action.tsx +15 -5
  280. package/ui/Link/Anchor.tsx +25 -13
  281. package/ui/Link/activePath.ts +25 -0
  282. package/ui/Link/index.tsx +1 -1
  283. package/ui/Load/Pagination.tsx +1 -1
  284. package/ui/Load/Units.tsx +1 -1
  285. package/ui/Loading/ProgressBar.tsx +1 -1
  286. package/ui/Modal.tsx +9 -0
  287. package/ui/Model/EditModal.tsx +16 -1
  288. package/ui/Model/New.tsx +25 -2
  289. package/ui/Model/NewWrapper_Client.tsx +2 -0
  290. package/ui/Model/ViewEditModal.tsx +25 -10
  291. package/ui/Model/ViewModal.tsx +16 -1
  292. package/ui/Model/newFormOrigin.ts +45 -0
  293. package/ui/Popconfirm.tsx +5 -7
  294. package/ui/Portal.tsx +4 -7
  295. package/ui/Reference/origin.ts +31 -0
  296. package/ui/ScreenNavigator.tsx +1 -1
  297. package/ui/Select.tsx +14 -14
  298. package/ui/Signal/Arg.tsx +2 -2
  299. package/ui/Signal/Doc.tsx +125 -63
  300. package/ui/Signal/Endpoint.tsx +11 -10
  301. package/ui/Signal/Listener.tsx +7 -10
  302. package/ui/Signal/Object.tsx +7 -14
  303. package/ui/Signal/Response.tsx +24 -24
  304. package/ui/Signal/RestApi.tsx +63 -52
  305. package/ui/Signal/WebSocket.tsx +65 -72
  306. package/ui/Signal/endpointEntries.ts +25 -0
  307. package/ui/Signal/index.ts +23 -6
  308. package/ui/Signal/signalText.ts +59 -0
  309. package/ui/System/CSR.tsx +41 -9
  310. package/ui/System/Client.tsx +37 -5
  311. package/ui/System/Common.tsx +76 -32
  312. package/ui/System/SSR.tsx +73 -31
  313. package/ui/Toast.tsx +1 -1
  314. package/ui/Tooltip.tsx +84 -30
  315. package/ui/index.ts +11 -1
  316. package/ui/overlayLayer.ts +25 -2
  317. package/ui/overlayPosition.ts +90 -0
  318. package/ui/recipe/inputRecipe.ts +8 -1
  319. package/ui/styles.css +152 -0
  320. package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeActivity.kt +22 -2
  321. package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeKernel.kt +37 -0
  322. package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeUpdateReports.kt +169 -0
  323. package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeUpdates.kt +110 -38
  324. package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeVectors.kt +20 -0
  325. package/vendor/@akanjs/native/native/desktop/Cargo.lock +16 -16
  326. package/vendor/@akanjs/native/native/desktop/src/external.rs +651 -0
  327. package/vendor/@akanjs/native/native/desktop/src/json.rs +23 -0
  328. package/vendor/@akanjs/native/native/desktop/src/lib.rs +228 -5
  329. package/vendor/@akanjs/native/native/ios/Sources/AkanNativeApp.swift +2 -1
  330. package/vendor/@akanjs/native/native/ios/Sources/AkanNativeKernel.swift +47 -1
  331. package/vendor/@akanjs/native/native/ios/Sources/AkanNativeUpdateReports.swift +145 -0
  332. package/vendor/@akanjs/native/native/ios/Sources/AkanNativeUpdates.swift +112 -43
  333. package/vendor/@akanjs/native/native/ios/Sources/AkanNativeVectors.swift +18 -0
  334. package/vendor/@akanjs/native/native/ios/Sources/AkanNativeViewController.swift +24 -2
  335. package/vendor/@akanjs/native/packages/cli/src/api.ts +22 -6
  336. package/vendor/@akanjs/native/packages/cli/src/commands/build.ts +24 -8
  337. package/vendor/@akanjs/native/packages/cli/src/commands/update.ts +11 -9
  338. package/vendor/@akanjs/native/packages/cli/src/config.ts +35 -7
  339. package/vendor/@akanjs/native/packages/cli/src/lib/compat.ts +2 -0
  340. package/vendor/@akanjs/native/packages/cli/src/lib/configkeys.ts +6 -4
  341. package/vendor/@akanjs/native/packages/cli/src/lib/desktop-server.ts +11 -1
  342. package/vendor/@akanjs/native/packages/cli/src/lib/distribution.ts +73 -0
  343. package/vendor/@akanjs/native/packages/cli/src/lib/project.ts +15 -3
  344. package/vendor/@akanjs/native/packages/cli/src/lib/publish.ts +70 -23
  345. package/vendor/@akanjs/native/packages/cli/src/lib/updates.ts +159 -23
  346. package/vendor/@akanjs/native/packages/cli/src/lib/vectors.ts +2 -0
  347. package/vendor/@akanjs/native/packages/cli/src/platforms/android.ts +9 -4
  348. package/vendor/@akanjs/native/packages/cli/src/platforms/desktop-update.ts +1 -1
  349. package/vendor/@akanjs/native/packages/cli/src/platforms/desktop.ts +4 -2
  350. package/vendor/@akanjs/native/packages/cli/src/platforms/ios.ts +9 -4
  351. package/vendor/@akanjs/native/packages/cli/src/platforms/windows-installer.ts +60 -21
  352. package/vendor/@akanjs/native/packages/cli/src/platforms/windows.ts +14 -3
  353. package/vendor/@akanjs/native/packages/core/src/kernel.ts +49 -0
  354. package/vendor/@akanjs/native/packages/core/vectors/updates.json +31 -0
  355. package/vendor/@akanjs/native/packages/desktop/src/dispatcher.ts +13 -5
  356. package/vendor/@akanjs/native/packages/desktop/src/main.ts +3 -0
  357. package/vendor/@akanjs/native/packages/desktop/src/plugin.ts +2 -0
  358. package/vendor/@akanjs/native/packages/desktop/src/server.ts +70 -6
  359. package/vendor/@akanjs/native/plugins/external-view/native-plugin.json +23 -0
  360. package/vendor/@akanjs/native/plugins/external-view/src/desktop.ts +321 -0
  361. package/vendor/@akanjs/native/plugins/external-view/src/index.ts +106 -0
  362. package/vendor/@akanjs/native/plugins/single-instance/src/desktop.ts +4 -0
  363. package/vendor/@akanjs/native/plugins/single-instance/src/socket.ts +12 -5
  364. package/vendor/@akanjs/native/plugins/updates/android/UpdatesPlugin.kt +133 -41
  365. package/vendor/@akanjs/native/plugins/updates/ios/UpdatesPlugin.swift +101 -36
  366. package/vendor/@akanjs/native/plugins/updates/native-plugin.json +4 -2
  367. package/vendor/@akanjs/native/plugins/updates/src/desktop.ts +15 -1
  368. package/vendor/@akanjs/native/plugins/updates/src/index.ts +7 -1
  369. package/vendor/@akanjs/native/plugins/window/native-plugin.json +4 -0
  370. package/vendor/@akanjs/native/plugins/window/src/desktop.ts +68 -1
  371. package/vendor/@akanjs/native/plugins/window/src/index.ts +47 -0
  372. package/vendor/use-agentic/AgentActivity.tsx +4 -2
  373. package/vendor/use-agentic/AgenticSurface.ts +3 -3
  374. package/vendor/use-agentic/ToolRunner.ts +8 -1
  375. package/vendor/use-agentic/types.ts +2 -0
  376. package/webkit/CsrRouteTable.ts +9 -1
  377. package/webkit/CsrStack.ts +9 -2
  378. package/webkit/EmbeddedLayout.tsx +77 -0
  379. package/webkit/McpAppAgent.ts +138 -0
  380. package/webkit/McpAppApprovals.ts +56 -0
  381. package/webkit/McpAppBridge.ts +291 -0
  382. package/webkit/McpAppView.ts +258 -0
  383. package/webkit/RenderLayer.tsx +20 -5
  384. package/webkit/bootCsr.tsx +54 -5
  385. package/webkit/index.ts +1 -0
  386. package/webkit/useCsrValues.ts +16 -10
  387. package/webkit/useFrameRuntime.ts +10 -1
  388. package/webkit/useLocation.ts +4 -3
  389. package/webkit/useVisualViewportFrame.tsx +52 -0
  390. package/service/predefinedAdaptor/sql/driver/libsql.ts +0 -45
  391. package/types/service/predefinedAdaptor/sql/driver/libsql.d.ts +0 -20
  392. package/types/ui/Agent/useKeyboardInset.d.ts +0 -1
  393. package/types/ui/ServerPortal.d.ts +0 -15
  394. package/ui/Agent/useKeyboardInset.ts +0 -19
  395. package/ui/ServerPortal.tsx +0 -49
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # akanjs
2
2
 
3
+ ## 3.0.2
4
+
5
+ ### Minor Changes
6
+
7
+ - be8ed50: Endpoints take a rate limit: `rateLimit: { calls, windowMs?, by?: "ip" | "account" } | false` in the signal option, and `option.setRateLimit({ budget, endpoints, accountKey, enabled })` in an app or lib `option.ts`. An `ip` budget is counted before the request body is read, an `account` budget after the middlewares and before the guards, and a refusal answers 429 `base.error.tooManyRequests` with `{ seconds }` and `Retry-After`. Counters share the cache across instances. `AKAN_RATE_LIMIT=off|on|<calls>[/<seconds>]`; unset, budgets apply everywhere but a `local` environment. A service counts a business key with `plug(RateLimit)` and `consume(key, { calls, windowMs })`. Fetches a page makes while rendering now carry the visitor's address, and the MCP limiter keys an anonymous caller by its trusted-proxy address instead of a raw `X-Real-IP` any caller could forge.
8
+ - af66669: The macOS dmg `build-desktop --installer true` makes opens on a drag-to-Applications window: no toolbar, sidebar or status bar, 128 pt icons of the app and the Applications link on a background with an arrow between them (1x and 2x in one multi-resolution TIFF), and the app's icon on the mounted disk (`.VolumeIcon.icns`). Its own files (`.background`, `.VolumeIcon.icns`, `.DS_Store`, `.fseventsd`) are hidden. The window is a `.DS_Store` written directly while a read-write image is mounted, never through Finder or AppleScript, so a headless CI builds the same image; the compressed image is made afterwards and is what gets signed, notarized and stapled. `native.desktop.dmg` (a target's too) replaces the background (`background`, `background2x`, or `false`) and moves the window and the icons (`window`, `iconSize`, `textSize`, `app`, `applications`, in points); the default background lines up with the default positions only.
9
+ - be8ed50: `buttonRecipe` gains `size: "xl"` and `inputRecipe` gains `kind: "select"` for a native `<select>`. Upgrade note: an app's own `recipes.button` / `recipes.input` override slot must accept the new values to typecheck.
10
+ - be8ed50: A slice's `get`/`cru`/`create`/`update`/`remove` guard may be `false`, which mounts no generated endpoint for that verb; `mcp: { cru: false }` only ever kept the verbs off the agent shelf. Upgrade note: a generated verb whose guards resolve to none is no longer mounted (it used to answer anyone over HTTP while the client never synthesized it). Resolving an endpoint class now warns about every endpoint that declares no guards.
11
+ - be8ed50: Reads and query-level writes can reach soft-removed rows with `{ withRemoved: true }` (`find`/`findOne`/`findById`'s third argument, `count`/`exists`/`updateMany`'s last, `updateOne`/`updateById`'s options). Upgrade note: a query with a `removedAt` condition only a removed row can meet (`q.exists("removedAt")`, a comparison) now throws `can never match` instead of silently matching nothing; add the flag where removed rows are meant. A removed document is read-only, and `updateById(id, { removedAt: null }, { withRemoved: true })` revives one.
12
+
13
+ ### Patch Changes
14
+
15
+ - be8ed50: An HTTP query or mutation returning `Binary` now answers base64, which the client reads back as bytes; it used to answer `{"0":1,…}`. A return declared `Any` that turns out to be an ArrayBuffer or typed array now fails with a message pointing at `Binary` instead of sending `{}`.
16
+ - af66669: A CSR page or layout whose render fails no longer leaves a blank screen. The layer shows the error's message — "Cannot reach the server. Check your connection and try again." for `base.error.serverUnreachable`, a generic "This page could not be loaded." otherwise — and a Try again button that runs the render again. A render that redirected before it failed still shows nothing, since its page is leaving.
17
+ - be8ed50: `enumOf` infers its literal values without `as const`, so a value outside the list fails to compile. Upgrade note: code that relied on a widened `string`/`number` enum type may need a narrower type.
18
+ - be8ed50: `String(err)` of a dictionary `Err` appends its `data` as one line of JSON, secret-named keys masked, so a log line keeps what failed.
19
+ - be8ed50: Arguments are parsed after the `account` guards and before the `resource` guards, so a caller an account guard refuses never reaches an argument parser. A guard or middleware that reads an argument earlier still gets it. Upgrade note: an unauthenticated call with a malformed argument now answers 401/403 instead of 400. A subscribed room's guards are re-checked on a fresh context, so a guard that memoizes per context gives a new verdict.
20
+ - be8ed50: Changing an `immutable` field is refused with a 400 `base.error.immutableField` the client can translate, instead of a generic 500.
21
+ - be8ed50: A server caller's `limit: 0` reads every row again, like an omitted limit, instead of 20. A client's `limit: 0` is served the 500-row page ceiling with a one-time warn.
22
+ - af66669: A Linux desktop app's window is named after its app id (X11 `WM_CLASS`, Wayland `app_id`), so a dock or task bar finds its `<app id>.desktop` and shows that entry's name and icon instead of a generic one; the dock plugin's badge and progress name the same entry. The AppImage `build-desktop --installer true` makes carries `<app id>.desktop` with `StartupWMClass=<app id>` and `<app id>.png`, so the entry AppImageLauncher or appimaged installs under another file name still matches the window. The hidden entry that handles the app's deep links now shows the app's icon (installed as `~/.local/share/icons/hicolor/256x256/apps/<app id>.png`), and from an AppImage it starts the AppImage itself (`$APPIMAGE`) rather than the mount point it ran from, which is gone once the app quits, so a link opened after quitting the app no longer fails.
23
+ - bd55713: `Input.Number` (and `Field.Number`) keeps the decimal point when Enter or Escape settles a typed value: 0.25 used to display as 25 while the stored value stayed 0.25.
24
+ - be8ed50: On a server, `fetch.clone({ origin })` toward another origin no longer opens a websocket unless `connect: true`, so a server making one-off calls to another server no longer leaks an auto-reconnecting socket per clone.
25
+ - be8ed50: `setCookie` defaults to `path=/; SameSite=Lax; Secure` instead of `SameSite=None`, matching the auth cookie the server sets, and an options object overrides only the keys it names.
26
+ - a789952: In a signal test every `fetch.clone()` reports its own client address, so an `ip` rate limit counts each agent apart instead of every agent sharing loopback; `clone({ clientIp })` names one explicitly.
27
+ - be8ed50: A Mongo-style `schema.index({ x: "text" })` no longer builds a B-tree over the JSON value (which could fail long inserts on Postgres); it warns at boot and points at the `text` field role, and drops the index an earlier boot built for exactly that declaration.
28
+
3
29
  ## 3.0.1
4
30
 
5
31
  ### Minor Changes
package/base/baseEnv.ts CHANGED
@@ -30,10 +30,6 @@ export type BackendEnv = {
30
30
  cacheSize?: number;
31
31
  tempStore?: string;
32
32
  };
33
- libsql?: {
34
- url?: string;
35
- authToken?: string;
36
- };
37
33
  postgres?: {
38
34
  url?: string;
39
35
  host?: string;
@@ -281,7 +281,7 @@ declare global {
281
281
  [CLIENT_VALUE]: Dayjs;
282
282
  [DEFAULT_VALUE]: Dayjs;
283
283
  [PURIFIED_VALUE]: Dayjs;
284
- [EXAMPLE_VALUE]: string;
284
+ [EXAMPLE_VALUE]: Dayjs;
285
285
  validate(value: Date): boolean;
286
286
  parseValue(input: Date): Dayjs;
287
287
  serializeValue(value: Dayjs | Date): Date;
@@ -371,7 +371,7 @@ Object.assign(Date, {
371
371
  ...scalarPrimitiveStatics,
372
372
  refName: "Date",
373
373
  [DEFAULT_VALUE]: dayjs(new Date(-1)),
374
- [EXAMPLE_VALUE]: dayjs(new Date().toISOString()),
374
+ [EXAMPLE_VALUE]: dayjs("2026-01-01T00:00:00.000Z"),
375
375
  validate(value: Date | string | number) {
376
376
  const date = new Date(value);
377
377
  if (Number.isNaN(date.getTime())) return false;
package/client/cookie.ts CHANGED
@@ -110,8 +110,12 @@ export const getStoredAuthToken = async (): Promise<string | undefined> => {
110
110
  * honours — the app-scoped auth cookie (a CSR client's bearer token) and `Authorization: Bearer` — so the two never
111
111
  * disagree on who is signed in. */
112
112
  export const getAccount = <AddData = unknown>(): Account<AddData> => {
113
- const jwt = getAuthToken() ?? getHeader("authorization")?.replace(/^Bearer\s+/i, "");
114
113
  const defaultAccount = { appName: getEnv().appName, environment: getEnv().environment } as Account<AddData>;
114
+
115
+ const embedded = globalThis.__AKAN_EMBED__;
116
+ if (embedded)
117
+ return embedded.account ? ({ ...defaultAccount, ...embedded.account } as Account<AddData>) : defaultAccount;
118
+ const jwt = getAuthToken() ?? getHeader("authorization")?.replace(/^Bearer\s+/i, "");
115
119
  if (!jwt) return defaultAccount;
116
120
  const account = decodeJwtPayload<Account<AddData>>(jwt);
117
121
  if (account.appName !== getEnv().appName || account.environment !== getEnv().environment) return defaultAccount;
@@ -0,0 +1,122 @@
1
+ interface CsrLocationEntry {
2
+ href: string;
3
+ state: unknown;
4
+ }
5
+
6
+ export interface CsrMemoryOwner {
7
+ leave: (href: string) => void;
8
+ reload: () => void;
9
+ }
10
+
11
+ declare global {
12
+ var __AKAN_CSR_LOCATION__: CsrLocation | undefined;
13
+
14
+ var __AKAN_EMBED__: { kind: "mcp-app"; origin?: string; account?: Record<string, unknown> | null } | undefined;
15
+ }
16
+
17
+ export class CsrLocation {
18
+ #memory: { entries: CsrLocationEntry[]; idx: number } | null = null;
19
+ #owner: CsrMemoryOwner = { leave: () => undefined, reload: () => undefined };
20
+
21
+ get isMemory() {
22
+ return this.#memory !== null;
23
+ }
24
+
25
+ /** The owner takes what a document load would do, which a view cannot: it has no document to load in its place. */
26
+ useMemory(href: string, owner: CsrMemoryOwner) {
27
+ this.#memory = { entries: [{ href, state: null }], idx: 0 };
28
+ this.#owner = owner;
29
+ }
30
+
31
+ /** Path, search and hash, without the origin. */
32
+ get href() {
33
+ const entry = this.#entry();
34
+ if (entry) return entry.href;
35
+ return `${window.location.pathname}${window.location.search}${window.location.hash}`;
36
+ }
37
+
38
+ get pathname() {
39
+ if (!this.#memory) return window.location.pathname;
40
+ return this.href.split(/[?#]/)[0] ?? "/";
41
+ }
42
+
43
+ get search() {
44
+ if (!this.#memory) return window.location.search;
45
+ const [beforeHash] = this.href.split("#");
46
+ const at = beforeHash?.indexOf("?") ?? -1;
47
+ return at >= 0 ? (beforeHash?.slice(at) ?? "") : "";
48
+ }
49
+
50
+ get hash() {
51
+ if (!this.#memory) return window.location.hash;
52
+ const at = this.href.indexOf("#");
53
+ return at >= 0 ? this.href.slice(at) : "";
54
+ }
55
+
56
+ get state(): unknown {
57
+ const entry = this.#entry();
58
+ return entry ? entry.state : window.history.state;
59
+ }
60
+
61
+ /** `href` as a route reads it: an absolute URL on this document's origin loses the origin, anything else is kept. */
62
+ strip(href: string) {
63
+ if (this.#memory) return href;
64
+ const origin = window.location.origin;
65
+ return origin !== "null" && href.startsWith(origin) ? href.slice(origin.length) : href;
66
+ }
67
+
68
+ push(state: unknown, href: string) {
69
+ const memory = this.#memory;
70
+ if (!memory) {
71
+ window.history.pushState(state, "", href);
72
+ return;
73
+ }
74
+ memory.entries = [...memory.entries.slice(0, memory.idx + 1), { href, state }];
75
+ memory.idx = memory.entries.length - 1;
76
+ }
77
+
78
+ replace(state: unknown, href?: string) {
79
+ const memory = this.#memory;
80
+ if (!memory) {
81
+ if (href === undefined) window.history.replaceState(state, "");
82
+ else window.history.replaceState(state, "", href);
83
+ return;
84
+ }
85
+ const current = memory.entries[memory.idx];
86
+ memory.entries[memory.idx] = { href: href ?? current?.href ?? "/", state };
87
+ }
88
+
89
+ back() {
90
+ const memory = this.#memory;
91
+ if (!memory) {
92
+ window.history.back();
93
+ return;
94
+ }
95
+ if (memory.idx === 0) return;
96
+ memory.idx -= 1;
97
+ const state = memory.entries[memory.idx]?.state ?? null;
98
+
99
+ setTimeout(() => window.dispatchEvent(new PopStateEvent("popstate", { state })), 0);
100
+ }
101
+
102
+ assign(href: string) {
103
+ if (!this.#memory) {
104
+ window.location.assign(href);
105
+ return;
106
+ }
107
+ this.#owner.leave(href);
108
+ }
109
+
110
+ reload() {
111
+ if (!this.#memory) window.location.reload();
112
+ else this.#owner.reload();
113
+ }
114
+
115
+ #entry() {
116
+ const memory = this.#memory;
117
+ return memory ? memory.entries[memory.idx] : undefined;
118
+ }
119
+ }
120
+
121
+ globalThis.__AKAN_CSR_LOCATION__ ??= new CsrLocation();
122
+ export const csrLocation: CsrLocation = globalThis.__AKAN_CSR_LOCATION__;
@@ -22,13 +22,22 @@ export type PageSafeAreaConfig =
22
22
  * `"block"` buffers the whole document, so an error in slow content can still render a clean error page. */
23
23
  export type SsrRenderMode = "stream" | "block";
24
24
 
25
+ /** Px per Tailwind breakpoint (sm 40rem, md 48rem, lg 64rem, xl 80rem, 2xl 96rem), each from its own width up. */
26
+ export interface ResponsiveInset {
27
+ base: number;
28
+ sm?: number;
29
+ md?: number;
30
+ lg?: number;
31
+ xl?: number;
32
+ "2xl"?: number;
33
+ }
25
34
  export interface PageConfig {
26
35
  transition?: TransitionType;
27
36
  safeArea?: PageSafeAreaConfig;
28
- /** Top chrome reservation in px. Use true for the default 48px reservation. */
29
- topInset?: number | boolean;
30
- /** Bottom chrome reservation in px. Use true for the default 48px reservation. */
31
- bottomInset?: number | boolean;
37
+ /** Top chrome reservation in px, or px per breakpoint. Use true for the default 48px reservation. */
38
+ topInset?: number | boolean | ResponsiveInset;
39
+ /** Bottom chrome reservation in px, or px per breakpoint. Use true for the default 48px reservation. */
40
+ bottomInset?: number | boolean | ResponsiveInset;
32
41
  gesture?: boolean;
33
42
  cache?: boolean;
34
43
  /** Initial full-document SSR strategy. Defaults to `"stream"`. */
@@ -48,6 +57,10 @@ export interface CsrState {
48
57
  bottomSafeArea: number;
49
58
  topInset: number;
50
59
  bottomInset: number;
60
+ /** The declared per-breakpoint insets; `topInset`/`bottomInset` hold their value at the width they resolved at. */
61
+ responsiveInset?: { top: ResponsiveInset; bottom: ResponsiveInset };
62
+ /** Web only: the sides whose safe area `env(safe-area-inset-*)` measures, since a browser tells script nothing. */
63
+ safeAreaEnv?: { top: boolean; bottom: boolean };
51
64
  gesture: boolean;
52
65
  cache: boolean;
53
66
  ssr: SsrRenderMode;
@@ -114,13 +127,27 @@ export interface RouteRender {
114
127
  resolveNotFound?: () => PromiseOrObject<LayoutNotFoundRender | undefined>;
115
128
  resolveError?: () => PromiseOrObject<LayoutErrorRender | undefined>;
116
129
  resolveHead?: ResolveHead;
130
+ /** Parses the declared args without rendering, so a path value the type refuses answers not-found before any byte. */
131
+ checkArgs?: (props: PageProps) => Promise<void>;
117
132
  getPageConfig?: () => PromiseOrObject<PageConfig | undefined>;
118
133
  getLayoutPageConfig?: () => PromiseOrObject<PageConfig | undefined>;
119
134
  /** A root layout's `theme`, readable before it renders. */
120
135
  getLayoutTheme?: () => PromiseOrObject<string | undefined>;
136
+ /** A root layout's `viewport`, readable before it renders: the document shell writes it ahead of the body. */
137
+ getLayoutViewport?: () => PromiseOrObject<RouteViewport | undefined>;
138
+ /** A root layout's `manifest`, which the server answers at the URL its documents link. */
139
+ getLayoutManifest?: () => PromiseOrObject<WebAppManifest | undefined>;
121
140
  /** The `page()` chain behind a page render, when it was declared as one — what a page prompt is read off. */
122
141
  getRouteDefinition?: () => PromiseOrObject<RouteDefinition | undefined>;
123
142
  }
143
+ export interface RouteViewport {
144
+ /** `viewport-fit=cover`: the page runs under the notch and home indicator, and `env(safe-area-inset-*)` measures them. */
145
+ fitCover?: boolean;
146
+ /** What the on-screen keyboard resizes; `resizes-content` shrinks the layout viewport, and `dvh` with it. */
147
+ interactiveWidget?: "resizes-visual" | "resizes-content" | "overlays-content";
148
+ /** The browser chrome's color: one for every theme, or one per theme name the app switches between. */
149
+ themeColor?: string | { [theme: string]: string };
150
+ }
124
151
  export interface WebAppManifestIcon {
125
152
  src: string;
126
153
  sizes?: string;
@@ -164,6 +191,7 @@ export interface LayoutModule {
164
191
  reconnect?: boolean;
165
192
  wsConnect?: boolean;
166
193
  layoutStyle?: "mobile" | "web";
194
+ viewport?: RouteViewport;
167
195
  Loading?: LayoutLoadingRender;
168
196
  NotFound?: LayoutNotFoundRender;
169
197
  Error?: LayoutErrorRender;
@@ -336,6 +364,10 @@ export const usePathCtx = () => {
336
364
  return contextValues;
337
365
  };
338
366
 
367
+ /** Inside a layout's own markup that an MCP view hides while the page is drawn on its own: frame chrome draws nothing. */
368
+ export const hiddenLayoutContext = sharedContext<boolean>("hiddenLayout", false);
369
+ export const useInHiddenLayout = () => useContext(hiddenLayoutContext);
370
+
339
371
  export type PageActivity = "current" | "prev" | "pending" | "hidden";
340
372
  export interface PageActivityState {
341
373
  activity: PageActivity;
package/client/device.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import type { RefObject } from "react";
3
+ import { csrLocation } from "./csrLocation";
3
4
  import { readCssSafeAreaInsets } from "./frameConfig";
4
5
  import type { DeviceInfo } from "./native";
5
6
 
@@ -98,7 +99,7 @@ const createWebDevice = ({
98
99
  lang?: string;
99
100
  supportLanguages: string[] | readonly string[];
100
101
  }) => {
101
- const pathname = typeof window === "undefined" ? "" : window.location.pathname;
102
+ const pathname = typeof window === "undefined" ? "" : csrLocation.pathname;
102
103
  return new Device({
103
104
  lang: lang ?? langInPath(pathname, supportLanguages) ?? getBrowserLanguage(),
104
105
  info: webDeviceInfo,
@@ -1,5 +1,12 @@
1
1
  import { isRecord } from "akanjs/common";
2
- import type { PageConfig, PageSafeAreaConfig, PageState, SsrRenderMode, TransitionType } from "./csrTypes";
2
+ import type {
3
+ PageConfig,
4
+ PageSafeAreaConfig,
5
+ PageState,
6
+ ResponsiveInset,
7
+ SsrRenderMode,
8
+ TransitionType,
9
+ } from "./csrTypes";
3
10
 
4
11
  export type DevicePlatform = "ios" | "android" | "web" | (string & {});
5
12
  export type SafeAreaInsets = { top: number; bottom: number };
@@ -11,8 +18,20 @@ export interface ResolvePageStateOptions {
11
18
  platform: DevicePlatform;
12
19
  deviceSafeArea: SafeAreaInsets;
13
20
  cssSafeArea?: SafeAreaInsets;
21
+ /** The viewport width in px a responsive inset resolves at; left out, it resolves at `base`. */
22
+ width?: number;
14
23
  }
15
24
 
25
+ /** Tailwind's default breakpoints in rem, the widths a `ResponsiveInset` keys on. */
26
+ export const insetBreakpoints = [
27
+ ["sm", 40],
28
+ ["md", 48],
29
+ ["lg", 64],
30
+ ["xl", 80],
31
+ ["2xl", 96],
32
+ ] as const;
33
+ export type InsetBreakpoint = "base" | (typeof insetBreakpoints)[number][0];
34
+
16
35
  const pageConfigKeys = new Set<keyof PageConfig>([
17
36
  "transition",
18
37
  "safeArea",
@@ -54,14 +73,42 @@ export function validatePageConfig(routeKey: string, config?: PageConfig) {
54
73
  for (const key of ["topInset", "bottomInset"] as const) {
55
74
  if (pageConfig[key] !== undefined && !isValidInsetValue(pageConfig[key]))
56
75
  throw new Error(
57
- `[route-convention] pageConfig.${key} in ${routeKey} must be a boolean or non-negative px number.`,
76
+ `[route-convention] pageConfig.${key} in ${routeKey} must be a boolean, a non-negative px number, or { base, sm?, md?, lg?, xl?, "2xl"? } of them.`,
58
77
  );
59
78
  }
60
79
  validateSafeAreaConfig(routeKey, pageConfig.safeArea);
61
80
  }
62
81
 
82
+ const isInsetPx = (value: unknown): value is number =>
83
+ typeof value === "number" && Number.isFinite(value) && value >= 0;
84
+ const responsiveInsetKeys = new Set<string>(["base", ...insetBreakpoints.map(([name]) => name)]);
85
+
63
86
  function isValidInsetValue(value: unknown): value is NonNullable<PageConfig["topInset"]> {
64
- return typeof value === "boolean" || (typeof value === "number" && Number.isFinite(value) && value >= 0);
87
+ if (typeof value === "boolean" || isInsetPx(value)) return true;
88
+ if (!isRecord(value) || !isInsetPx(value.base)) return false;
89
+ return Object.entries(value).every(([key, px]) => responsiveInsetKeys.has(key) && isInsetPx(px));
90
+ }
91
+
92
+ export const isResponsiveInset = (inset: PageConfig["topInset"]): inset is ResponsiveInset => isRecord(inset);
93
+
94
+ /** The inset at each breakpoint, each one the nearest declared value at or below it. */
95
+ export function insetsByBreakpoint(inset: PageConfig["topInset"]): { [bp in InsetBreakpoint]: number } {
96
+ const base = isResponsiveInset(inset) ? inset.base : resolveInset(inset);
97
+ const values = { base } as { [bp in InsetBreakpoint]: number };
98
+ let current = base;
99
+ for (const [name] of insetBreakpoints) {
100
+ current = (isResponsiveInset(inset) ? inset[name] : undefined) ?? current;
101
+ values[name] = current;
102
+ }
103
+ return values;
104
+ }
105
+
106
+ /** The inset in force at `width` px, assuming a 16px rem. */
107
+ export function insetAt(inset: PageConfig["topInset"], width?: number): number {
108
+ if (!isResponsiveInset(inset)) return resolveInset(inset);
109
+ if (width === undefined) return inset.base;
110
+ const values = insetsByBreakpoint(inset);
111
+ return insetBreakpoints.reduce((value, [name, rem]) => (width >= rem * 16 ? values[name] : value), values.base);
65
112
  }
66
113
 
67
114
  function validateSafeAreaConfig(routeKey: string, safeArea?: PageSafeAreaConfig) {
@@ -132,6 +179,7 @@ export function resolvePageState({
132
179
  platform,
133
180
  deviceSafeArea,
134
181
  cssSafeArea,
182
+ width,
135
183
  }: ResolvePageStateOptions): PageState {
136
184
  const profile = getPlatformFrameProfile(platform);
137
185
  const routeRole = getRouteRoleFrameDefaults(path, platform, basePath);
@@ -144,12 +192,25 @@ export function resolvePageState({
144
192
  cssSafeArea,
145
193
  });
146
194
  const transition = config.transition ?? "none";
195
+ const responsive = isResponsiveInset(config.topInset) || isResponsiveInset(config.bottomInset);
196
+ const safeAreaEnv = platform === "web" ? resolveSafeAreaEnv(config.safeArea) : undefined;
147
197
  return {
148
198
  transition,
149
199
  topSafeArea: safeArea.top,
150
200
  bottomSafeArea: safeArea.bottom,
151
- topInset: resolveInset(config.topInset),
152
- bottomInset: resolveInset(config.bottomInset),
201
+ topInset: insetAt(config.topInset, width),
202
+ bottomInset: insetAt(config.bottomInset, width),
203
+ ...(responsive
204
+ ? {
205
+ responsiveInset: {
206
+ top: isResponsiveInset(config.topInset) ? config.topInset : { base: resolveInset(config.topInset) },
207
+ bottom: isResponsiveInset(config.bottomInset)
208
+ ? config.bottomInset
209
+ : { base: resolveInset(config.bottomInset) },
210
+ },
211
+ }
212
+ : {}),
213
+ ...(safeAreaEnv ? { safeAreaEnv } : {}),
153
214
  gesture: explicitKeys.gesture
154
215
  ? (config.gesture ?? false)
155
216
  : transition === "none"
@@ -165,9 +226,17 @@ export function resolvePageState({
165
226
  function resolveInset(inset: PageConfig["topInset"]): number {
166
227
  if (inset === true) return DEFAULT_BOOLEAN_INSET;
167
228
  if (inset === false || inset === undefined) return 0;
229
+ if (isResponsiveInset(inset)) return inset.base;
168
230
  return inset;
169
231
  }
170
232
 
233
+ function resolveSafeAreaEnv(safeArea?: PageSafeAreaConfig): { top: boolean; bottom: boolean } | undefined {
234
+ if (safeArea === undefined || safeArea === false) return undefined;
235
+ const top = safeArea === true || safeArea === "top" || (isRecord(safeArea) && safeArea.top !== false);
236
+ const bottom = safeArea === true || safeArea === "bottom" || (isRecord(safeArea) && safeArea.bottom !== false);
237
+ return top || bottom ? { top, bottom } : undefined;
238
+ }
239
+
171
240
  function getPlatformFrameProfile(platform: DevicePlatform): PageConfig {
172
241
  if (platform === "ios") return { safeArea: true, transition: "stack" };
173
242
  if (platform === "android") return { safeArea: { android: "auto" }, transition: "scaleOut", gesture: false };
@@ -221,9 +290,11 @@ function resolveSafeArea({
221
290
  bottom: bottomEnabled && source ? source.bottom : 0,
222
291
  };
223
292
  }
293
+
294
+ const source = platform === "web" && cssSafeArea ? cssSafeArea : deviceSafeArea;
224
295
  return {
225
- top: topEnabled ? deviceSafeArea.top : 0,
226
- bottom: bottomEnabled ? deviceSafeArea.bottom : 0,
296
+ top: topEnabled ? source.top : 0,
297
+ bottom: bottomEnabled ? source.bottom : 0,
227
298
  };
228
299
  }
229
300
 
package/client/index.ts CHANGED
@@ -2,6 +2,7 @@ export * from "./clientRuntime";
2
2
  export * from "./cn";
3
3
  export * from "./cookie";
4
4
  export * from "./createFont";
5
+ export * from "./csrLocation";
5
6
  export * from "./csrTypes";
6
7
  export * from "./decorators";
7
8
  export * from "./device";
@@ -17,3 +18,5 @@ export * from "./storage";
17
18
  export * from "./translator";
18
19
  export * from "./types";
19
20
  export * from "./useClient";
21
+ export * from "./viewportMeta";
22
+ export * from "./webManifest";
@@ -2,6 +2,7 @@ import { getEnv } from "akanjs/base";
2
2
  import { parseAkanI18nEnv } from "akanjs/common";
3
3
  import { untrackedHeaders, untrackedRequest } from "akanjs/fetch";
4
4
  import type { ReactNode } from "react";
5
+ import { csrLocation } from "./csrLocation";
5
6
  import { Translator } from "./translator";
6
7
 
7
8
  type TransMessage<Locale extends Record<string, unknown>> = {
@@ -27,11 +28,11 @@ const getPageInfo = (): { locale: string; path: string } => {
27
28
  const activePath = Translator.getActivePath();
28
29
  if (activePath) return { locale: activeLocale ?? defaultLocale, path: activePath };
29
30
  if (getEnv().side !== "server") {
30
- const [, firstSegment = "", ...rest] = window.location.pathname.split("/");
31
+ const [, firstSegment = "", ...rest] = csrLocation.pathname.split("/");
31
32
  const hasLocalePrefix = localeSet.has(firstSegment);
32
33
 
33
34
  const locale = activeLocale ?? (hasLocalePrefix ? firstSegment : defaultLocale);
34
- return { locale, path: activePath ?? (hasLocalePrefix ? `/${rest.join("/")}` : window.location.pathname) };
35
+ return { locale, path: activePath ?? (hasLocalePrefix ? `/${rest.join("/")}` : csrLocation.pathname) };
35
36
  }
36
37
  const h = untrackedHeaders();
37
38
 
@@ -0,0 +1 @@
1
+ export * from "../../vendor/@akanjs/native/plugins/external-view/src/index.ts";
@@ -1,4 +1,4 @@
1
- import type { LayoutModule, PageModule, WebAppManifest } from "../csrTypes";
1
+ import type { LayoutModule, PageModule, RouteViewport, WebAppManifest } from "../csrTypes";
2
2
  import type { ReactFont } from "../types";
3
3
  import { LayoutDefinition } from "./LayoutDefinition";
4
4
  import type { RouteArgsShape, RouteKind } from "./RouteDefinition";
@@ -12,6 +12,7 @@ export class RootLayoutDefinition<Args extends RouteArgsShape = Record<never, ne
12
12
  #reconnect?: boolean;
13
13
  #wsConnect?: boolean;
14
14
  #layoutStyle?: "mobile" | "web";
15
+ #viewport?: RouteViewport;
15
16
 
16
17
  fonts(fonts: ReactFont[]) {
17
18
  this.#fonts = fonts;
@@ -37,6 +38,10 @@ export class RootLayoutDefinition<Args extends RouteArgsShape = Record<never, ne
37
38
  this.#layoutStyle = style;
38
39
  return this;
39
40
  }
41
+ viewport(viewport: RouteViewport) {
42
+ this.#viewport = viewport;
43
+ return this;
44
+ }
40
45
 
41
46
  protected override extendModule(module: PageModule & LayoutModule): PageModule & LayoutModule {
42
47
  return {
@@ -47,6 +52,7 @@ export class RootLayoutDefinition<Args extends RouteArgsShape = Record<never, ne
47
52
  ...(this.#reconnect !== undefined ? { reconnect: this.#reconnect } : {}),
48
53
  ...(this.#wsConnect !== undefined ? { wsConnect: this.#wsConnect } : {}),
49
54
  ...(this.#layoutStyle ? { layoutStyle: this.#layoutStyle } : {}),
55
+ ...(this.#viewport ? { viewport: this.#viewport } : {}),
50
56
  };
51
57
  }
52
58
  }
@@ -0,0 +1,108 @@
1
+ import { cloneElement, createElement, Fragment, isValidElement, type ReactElement, type ReactNode } from "react";
2
+ import type { AkanHeadSnapshotNode, AkanHeadSnapshotV1, ResolvedHead, ResolveHeadResult } from "../csrTypes";
3
+
4
+ type TagProps = Record<string, unknown>;
5
+
6
+ /** Merges the `.head()` of every route level, root first: a deeper level takes over each singleton tag it writes. */
7
+ export class RouteHead {
8
+
9
+ static readonly #singletonLinkRels = new Set([
10
+ "canonical",
11
+ "manifest",
12
+ "icon",
13
+ "shortcut icon",
14
+ "apple-touch-icon",
15
+ "mask-icon",
16
+ ]);
17
+
18
+ static merge(levels: ResolveHeadResult[]): ResolveHeadResult {
19
+ const present = levels.filter((level) => RouteHead.#nodeOf(level) != null || RouteHead.#snapshotOf(level));
20
+ if (present.length <= 1) return present[0];
21
+ const node = RouteHead.mergeNodes(present.map((level) => RouteHead.#nodeOf(level)));
22
+ const snapshots = present.map((level) => RouteHead.#snapshotOf(level)).filter((snapshot) => !!snapshot);
23
+ if (!snapshots.length) return node;
24
+ return { node, headSnapshot: RouteHead.#mergeSnapshots(snapshots) } satisfies ResolvedHead;
25
+ }
26
+
27
+ /** What makes two tags the same tag; `null` for one that never replaces another. */
28
+ static keyOf(tag: string, props: TagProps): string | null {
29
+ if (tag === "title" || tag === "base") return tag;
30
+ if (tag === "meta") {
31
+ if (props.charSet !== undefined || props.charset !== undefined) return "meta:charset";
32
+ for (const attr of ["name", "property", "httpEquiv", "itemProp"]) {
33
+ const value = props[attr];
34
+ if (typeof value === "string" && value) return `meta:${attr}:${value.toLowerCase()}`;
35
+ }
36
+ return null;
37
+ }
38
+ if (tag !== "link" || typeof props.rel !== "string") return null;
39
+ const rel = props.rel.toLowerCase();
40
+ if (RouteHead.#singletonLinkRels.has(rel)) return `link:${rel}`;
41
+ const hrefLang = props.hrefLang ?? props.hreflang;
42
+ if (rel === "alternate" && typeof hrefLang === "string") return `link:alternate:${hrefLang.toLowerCase()}`;
43
+ return null;
44
+ }
45
+
46
+ static #nodeOf(level: ResolveHeadResult): ReactNode {
47
+ return RouteHead.#isResolved(level) ? level.node : level;
48
+ }
49
+
50
+ static #snapshotOf(level: ResolveHeadResult): AkanHeadSnapshotV1 | undefined {
51
+ return RouteHead.#isResolved(level) ? level.headSnapshot : undefined;
52
+ }
53
+
54
+ static #isResolved(level: ResolveHeadResult): level is ResolvedHead {
55
+ return typeof level === "object" && level !== null && !isValidElement(level) && "node" in level;
56
+ }
57
+
58
+ /** `merge` for plain JSX heads, root first. */
59
+ static mergeNodes(nodes: ReactNode[]): ReactNode {
60
+ const present = nodes.filter((node) => node !== null && node !== undefined);
61
+ if (present.length <= 1) return present[0];
62
+ const levels = present.map((node) => RouteHead.#flatten(node));
63
+ const kept = RouteHead.#keepDeepest(levels, (child) =>
64
+ isValidElement(child) && typeof child.type === "string"
65
+ ? RouteHead.keyOf(child.type, child.props as TagProps)
66
+ : null,
67
+ );
68
+
69
+ const children = kept.map(({ item, level, idx }) =>
70
+ isValidElement(item) ? cloneElement(item as ReactElement, { key: `head:${level}:${idx}` }) : item,
71
+ );
72
+ return createElement(Fragment, null, ...children);
73
+ }
74
+
75
+ static #mergeSnapshots(snapshots: AkanHeadSnapshotV1[]): AkanHeadSnapshotV1 {
76
+ const kept = RouteHead.#keepDeepest(
77
+ snapshots.map((snapshot) => snapshot.nodes),
78
+ (node: AkanHeadSnapshotNode) => RouteHead.keyOf(node.tag, node.attrs ?? {}),
79
+ );
80
+ return { version: 1, nodes: kept.map(({ item }) => item) };
81
+ }
82
+
83
+ static #keepDeepest<T>(levels: T[][], keyOf: (item: T) => string | null) {
84
+ const owner = new Map<string, number>();
85
+ levels.forEach((items, level) => {
86
+ for (const item of items) {
87
+ const key = keyOf(item);
88
+ if (key) owner.set(key, level);
89
+ }
90
+ });
91
+ return levels.flatMap((items, level) =>
92
+ items
93
+ .map((item, idx) => ({ item, level, idx }))
94
+ .filter(({ item }) => {
95
+ const key = keyOf(item);
96
+ return !key || owner.get(key) === level;
97
+ }),
98
+ );
99
+ }
100
+
101
+ static #flatten(node: ReactNode): ReactNode[] {
102
+ if (node === null || node === undefined || typeof node === "boolean") return [];
103
+ if (Array.isArray(node)) return node.flatMap((child: ReactNode) => RouteHead.#flatten(child));
104
+ if (isValidElement(node) && node.type === Fragment)
105
+ return RouteHead.#flatten((node.props as { children?: ReactNode }).children);
106
+ return [node];
107
+ }
108
+ }
@@ -2,6 +2,7 @@ export * from "./LayoutDefinition";
2
2
  export * from "./PageDefinition";
3
3
  export * from "./RootLayoutDefinition";
4
4
  export * from "./RouteDefinition";
5
+ export * from "./RouteHead";
5
6
  export * from "./resolveRouteModule";
6
7
  export * from "./routeArgs";
7
8
  export * from "./routeBuilders";