@astrale-os/shell-react 0.1.0

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 (378) hide show
  1. package/LICENSE +202 -0
  2. package/dist/application/apps/app-host.d.ts +31 -0
  3. package/dist/application/apps/app-host.d.ts.map +1 -0
  4. package/dist/application/apps/app-host.js +59 -0
  5. package/dist/application/apps/app-logo.d.ts +19 -0
  6. package/dist/application/apps/app-logo.d.ts.map +1 -0
  7. package/dist/application/apps/app-logo.js +31 -0
  8. package/dist/application/apps/apps.hook.d.ts +12 -0
  9. package/dist/application/apps/apps.hook.d.ts.map +1 -0
  10. package/dist/application/apps/apps.hook.js +51 -0
  11. package/dist/application/apps/open-app.hook.d.ts +10 -0
  12. package/dist/application/apps/open-app.hook.d.ts.map +1 -0
  13. package/dist/application/apps/open-app.hook.js +7 -0
  14. package/dist/application/index.d.ts +18 -0
  15. package/dist/application/index.d.ts.map +1 -0
  16. package/dist/application/index.js +12 -0
  17. package/dist/application/roles.hook.d.ts +7 -0
  18. package/dist/application/roles.hook.d.ts.map +1 -0
  19. package/dist/application/roles.hook.js +23 -0
  20. package/dist/application/spaces/current-space.hook.d.ts +7 -0
  21. package/dist/application/spaces/current-space.hook.d.ts.map +1 -0
  22. package/dist/application/spaces/current-space.hook.js +27 -0
  23. package/dist/application/spaces/current-space.store.d.ts +32 -0
  24. package/dist/application/spaces/current-space.store.d.ts.map +1 -0
  25. package/dist/application/spaces/current-space.store.js +79 -0
  26. package/dist/application/spaces/space.hook.d.ts +5 -0
  27. package/dist/application/spaces/space.hook.d.ts.map +1 -0
  28. package/dist/application/spaces/space.hook.js +25 -0
  29. package/dist/application/spaces/spaces.hook.d.ts +14 -0
  30. package/dist/application/spaces/spaces.hook.d.ts.map +1 -0
  31. package/dist/application/spaces/spaces.hook.js +82 -0
  32. package/dist/application/user/user.hook.d.ts +10 -0
  33. package/dist/application/user/user.hook.d.ts.map +1 -0
  34. package/dist/application/user/user.hook.js +19 -0
  35. package/dist/application/user/user.store.d.ts +35 -0
  36. package/dist/application/user/user.store.d.ts.map +1 -0
  37. package/dist/application/user/user.store.js +51 -0
  38. package/dist/auth/act-as.context.d.ts +40 -0
  39. package/dist/auth/act-as.context.d.ts.map +1 -0
  40. package/dist/auth/act-as.context.js +84 -0
  41. package/dist/auth/auth.hook.d.ts +3 -0
  42. package/dist/auth/auth.hook.d.ts.map +1 -0
  43. package/dist/auth/auth.hook.js +25 -0
  44. package/dist/auth/can.hook.d.ts +40 -0
  45. package/dist/auth/can.hook.d.ts.map +1 -0
  46. package/dist/auth/can.hook.js +119 -0
  47. package/dist/auth/index.d.ts +6 -0
  48. package/dist/auth/index.d.ts.map +1 -0
  49. package/dist/auth/index.js +5 -0
  50. package/dist/auth/self.hook.d.ts +3 -0
  51. package/dist/auth/self.hook.d.ts.map +1 -0
  52. package/dist/auth/self.hook.js +11 -0
  53. package/dist/auth/session-kernel.d.ts +4 -0
  54. package/dist/auth/session-kernel.d.ts.map +1 -0
  55. package/dist/auth/session-kernel.js +21 -0
  56. package/dist/graph/action.d.ts +22 -0
  57. package/dist/graph/action.d.ts.map +1 -0
  58. package/dist/graph/action.js +85 -0
  59. package/dist/graph/async.d.ts +11 -0
  60. package/dist/graph/async.d.ts.map +1 -0
  61. package/dist/graph/async.js +60 -0
  62. package/dist/graph/call/call.hook.d.ts +21 -0
  63. package/dist/graph/call/call.hook.d.ts.map +1 -0
  64. package/dist/graph/call/call.hook.js +23 -0
  65. package/dist/graph/call/stream.hook.d.ts +28 -0
  66. package/dist/graph/call/stream.hook.d.ts.map +1 -0
  67. package/dist/graph/call/stream.hook.js +137 -0
  68. package/dist/graph/graph.context.d.ts +4 -0
  69. package/dist/graph/graph.context.d.ts.map +1 -0
  70. package/dist/graph/graph.context.js +10 -0
  71. package/dist/graph/guards.error.d.ts +9 -0
  72. package/dist/graph/guards.error.d.ts.map +1 -0
  73. package/dist/graph/guards.error.js +7 -0
  74. package/dist/graph/index.d.ts +20 -0
  75. package/dist/graph/index.d.ts.map +1 -0
  76. package/dist/graph/index.js +9 -0
  77. package/dist/graph/memory/base.d.ts +21 -0
  78. package/dist/graph/memory/base.d.ts.map +1 -0
  79. package/dist/graph/memory/base.js +33 -0
  80. package/dist/graph/memory/read/envelope.d.ts +71 -0
  81. package/dist/graph/memory/read/envelope.d.ts.map +1 -0
  82. package/dist/graph/memory/read/envelope.js +29 -0
  83. package/dist/graph/memory/read/idle.d.ts +18 -0
  84. package/dist/graph/memory/read/idle.d.ts.map +1 -0
  85. package/dist/graph/memory/read/idle.js +16 -0
  86. package/dist/graph/memory/read/read.d.ts +9 -0
  87. package/dist/graph/memory/read/read.d.ts.map +1 -0
  88. package/dist/graph/memory/read/read.js +87 -0
  89. package/dist/graph/memory/scope.d.ts +7 -0
  90. package/dist/graph/memory/scope.d.ts.map +1 -0
  91. package/dist/graph/memory/scope.js +25 -0
  92. package/dist/graph/mutate/move.hook.d.ts +7 -0
  93. package/dist/graph/mutate/move.hook.d.ts.map +1 -0
  94. package/dist/graph/mutate/move.hook.js +49 -0
  95. package/dist/graph/mutate/mutate.hook.d.ts +17 -0
  96. package/dist/graph/mutate/mutate.hook.d.ts.map +1 -0
  97. package/dist/graph/mutate/mutate.hook.js +45 -0
  98. package/dist/graph/query/children.hook.d.ts +29 -0
  99. package/dist/graph/query/children.hook.d.ts.map +1 -0
  100. package/dist/graph/query/children.hook.js +64 -0
  101. package/dist/graph/query/graph.hook.d.ts +9 -0
  102. package/dist/graph/query/graph.hook.d.ts.map +1 -0
  103. package/dist/graph/query/graph.hook.js +5 -0
  104. package/dist/graph/query/neighbors.hook.d.ts +7 -0
  105. package/dist/graph/query/neighbors.hook.d.ts.map +1 -0
  106. package/dist/graph/query/neighbors.hook.js +79 -0
  107. package/dist/graph/query/node.hook.d.ts +14 -0
  108. package/dist/graph/query/node.hook.d.ts.map +1 -0
  109. package/dist/graph/query/node.hook.js +43 -0
  110. package/dist/graph/query/query.hook.d.ts +14 -0
  111. package/dist/graph/query/query.hook.d.ts.map +1 -0
  112. package/dist/graph/query/query.hook.js +23 -0
  113. package/dist/index.d.ts +43 -0
  114. package/dist/index.d.ts.map +1 -0
  115. package/dist/index.js +34 -0
  116. package/dist/intents/envelope.d.ts +33 -0
  117. package/dist/intents/envelope.d.ts.map +1 -0
  118. package/dist/intents/envelope.js +19 -0
  119. package/dist/intents/index.d.ts +6 -0
  120. package/dist/intents/index.d.ts.map +1 -0
  121. package/dist/intents/index.js +3 -0
  122. package/dist/intents/listen-intent.hook.d.ts +16 -0
  123. package/dist/intents/listen-intent.hook.d.ts.map +1 -0
  124. package/dist/intents/listen-intent.hook.js +75 -0
  125. package/dist/intents/reply.d.ts +9 -0
  126. package/dist/intents/reply.d.ts.map +1 -0
  127. package/dist/intents/reply.js +14 -0
  128. package/dist/intents/send-intent.hook.d.ts +12 -0
  129. package/dist/intents/send-intent.hook.d.ts.map +1 -0
  130. package/dist/intents/send-intent.hook.js +86 -0
  131. package/dist/navigation/adapters/adapter.d.ts +15 -0
  132. package/dist/navigation/adapters/adapter.d.ts.map +1 -0
  133. package/dist/navigation/adapters/adapter.js +1 -0
  134. package/dist/navigation/adapters/memory.adapter.d.ts +9 -0
  135. package/dist/navigation/adapters/memory.adapter.d.ts.map +1 -0
  136. package/dist/navigation/adapters/memory.adapter.js +15 -0
  137. package/dist/navigation/adapters/url.adapter.d.ts +6 -0
  138. package/dist/navigation/adapters/url.adapter.d.ts.map +1 -0
  139. package/dist/navigation/adapters/url.adapter.js +59 -0
  140. package/dist/navigation/history.d.ts +63 -0
  141. package/dist/navigation/history.d.ts.map +1 -0
  142. package/dist/navigation/history.js +144 -0
  143. package/dist/navigation/hooks/ancestors.hook.d.ts +13 -0
  144. package/dist/navigation/hooks/ancestors.hook.d.ts.map +1 -0
  145. package/dist/navigation/hooks/ancestors.hook.js +56 -0
  146. package/dist/navigation/hooks/location.hook.d.ts +4 -0
  147. package/dist/navigation/hooks/location.hook.d.ts.map +1 -0
  148. package/dist/navigation/hooks/location.hook.js +17 -0
  149. package/dist/navigation/hooks/navigate.hook.d.ts +12 -0
  150. package/dist/navigation/hooks/navigate.hook.d.ts.map +1 -0
  151. package/dist/navigation/hooks/navigate.hook.js +33 -0
  152. package/dist/navigation/hooks/scope.hook.d.ts +12 -0
  153. package/dist/navigation/hooks/scope.hook.d.ts.map +1 -0
  154. package/dist/navigation/hooks/scope.hook.js +21 -0
  155. package/dist/navigation/index.d.ts +16 -0
  156. package/dist/navigation/index.d.ts.map +1 -0
  157. package/dist/navigation/index.js +10 -0
  158. package/dist/navigation/link.d.ts +11 -0
  159. package/dist/navigation/link.d.ts.map +1 -0
  160. package/dist/navigation/link.js +62 -0
  161. package/dist/navigation/location.d.ts +48 -0
  162. package/dist/navigation/location.d.ts.map +1 -0
  163. package/dist/navigation/location.js +88 -0
  164. package/dist/navigation/scope.context.d.ts +64 -0
  165. package/dist/navigation/scope.context.d.ts.map +1 -0
  166. package/dist/navigation/scope.context.js +178 -0
  167. package/dist/navigation/snapshot.d.ts +22 -0
  168. package/dist/navigation/snapshot.d.ts.map +1 -0
  169. package/dist/navigation/snapshot.js +65 -0
  170. package/dist/schema/bind-read.d.ts +37 -0
  171. package/dist/schema/bind-read.d.ts.map +1 -0
  172. package/dist/schema/bind-read.js +205 -0
  173. package/dist/schema/binding.d.ts +21 -0
  174. package/dist/schema/binding.d.ts.map +1 -0
  175. package/dist/schema/binding.js +55 -0
  176. package/dist/schema/domain.hook.d.ts +10 -0
  177. package/dist/schema/domain.hook.d.ts.map +1 -0
  178. package/dist/schema/domain.hook.js +7 -0
  179. package/dist/schema/function.hook.d.ts +9 -0
  180. package/dist/schema/function.hook.d.ts.map +1 -0
  181. package/dist/schema/function.hook.js +40 -0
  182. package/dist/schema/index.d.ts +10 -0
  183. package/dist/schema/index.d.ts.map +1 -0
  184. package/dist/schema/index.js +8 -0
  185. package/dist/schema/kernel.hook.d.ts +10 -0
  186. package/dist/schema/kernel.hook.d.ts.map +1 -0
  187. package/dist/schema/kernel.hook.js +8 -0
  188. package/dist/schema/method.hook.d.ts +14 -0
  189. package/dist/schema/method.hook.d.ts.map +1 -0
  190. package/dist/schema/method.hook.js +49 -0
  191. package/dist/schema/tag.d.ts +11 -0
  192. package/dist/schema/tag.d.ts.map +1 -0
  193. package/dist/schema/tag.js +11 -0
  194. package/dist/schema/types.d.ts +73 -0
  195. package/dist/schema/types.d.ts.map +1 -0
  196. package/dist/schema/types.js +1 -0
  197. package/dist/schema/write.d.ts +15 -0
  198. package/dist/schema/write.d.ts.map +1 -0
  199. package/dist/schema/write.js +45 -0
  200. package/dist/selection/index.d.ts +4 -0
  201. package/dist/selection/index.d.ts.map +1 -0
  202. package/dist/selection/index.js +4 -0
  203. package/dist/selection/live.d.ts +21 -0
  204. package/dist/selection/live.d.ts.map +1 -0
  205. package/dist/selection/live.js +116 -0
  206. package/dist/selection/scope.context.d.ts +26 -0
  207. package/dist/selection/scope.context.d.ts.map +1 -0
  208. package/dist/selection/scope.context.js +100 -0
  209. package/dist/selection/selection.hook.d.ts +4 -0
  210. package/dist/selection/selection.hook.d.ts.map +1 -0
  211. package/dist/selection/selection.hook.js +14 -0
  212. package/dist/session/boot/boot.d.ts +19 -0
  213. package/dist/session/boot/boot.d.ts.map +1 -0
  214. package/dist/session/boot/boot.js +45 -0
  215. package/dist/session/boot/ownership.d.ts +18 -0
  216. package/dist/session/boot/ownership.d.ts.map +1 -0
  217. package/dist/session/boot/ownership.js +39 -0
  218. package/dist/session/boot/status.d.ts +24 -0
  219. package/dist/session/boot/status.d.ts.map +1 -0
  220. package/dist/session/boot/status.js +11 -0
  221. package/dist/session/connection/connection.hook.d.ts +3 -0
  222. package/dist/session/connection/connection.hook.d.ts.map +1 -0
  223. package/dist/session/connection/connection.hook.js +12 -0
  224. package/dist/session/connection/connection.store.d.ts +24 -0
  225. package/dist/session/connection/connection.store.d.ts.map +1 -0
  226. package/dist/session/connection/connection.store.js +31 -0
  227. package/dist/session/index.d.ts +8 -0
  228. package/dist/session/index.d.ts.map +1 -0
  229. package/dist/session/index.js +6 -0
  230. package/dist/session/kernel-client.hook.d.ts +9 -0
  231. package/dist/session/kernel-client.hook.d.ts.map +1 -0
  232. package/dist/session/kernel-client.hook.js +5 -0
  233. package/dist/session/kernel-instance.hook.d.ts +9 -0
  234. package/dist/session/kernel-instance.hook.d.ts.map +1 -0
  235. package/dist/session/kernel-instance.hook.js +10 -0
  236. package/dist/session/provider.d.ts +31 -0
  237. package/dist/session/provider.d.ts.map +1 -0
  238. package/dist/session/provider.js +165 -0
  239. package/dist/session/session.context.d.ts +18 -0
  240. package/dist/session/session.context.d.ts.map +1 -0
  241. package/dist/session/session.context.js +8 -0
  242. package/dist/session/shell.hook.d.ts +4 -0
  243. package/dist/session/shell.hook.d.ts.map +1 -0
  244. package/dist/session/shell.hook.js +15 -0
  245. package/dist/window/adopt.d.ts +27 -0
  246. package/dist/window/adopt.d.ts.map +1 -0
  247. package/dist/window/adopt.js +27 -0
  248. package/dist/window/hooks/close-guard.hook.d.ts +6 -0
  249. package/dist/window/hooks/close-guard.hook.d.ts.map +1 -0
  250. package/dist/window/hooks/close-guard.hook.js +90 -0
  251. package/dist/window/hooks/target-node.hook.d.ts +7 -0
  252. package/dist/window/hooks/target-node.hook.d.ts.map +1 -0
  253. package/dist/window/hooks/target-node.hook.js +77 -0
  254. package/dist/window/hooks/views-for.hook.d.ts +8 -0
  255. package/dist/window/hooks/views-for.hook.d.ts.map +1 -0
  256. package/dist/window/hooks/views-for.hook.js +51 -0
  257. package/dist/window/hooks/window.hook.d.ts +4 -0
  258. package/dist/window/hooks/window.hook.d.ts.map +1 -0
  259. package/dist/window/hooks/window.hook.js +14 -0
  260. package/dist/window/hooks/windows.hook.d.ts +15 -0
  261. package/dist/window/hooks/windows.hook.d.ts.map +1 -0
  262. package/dist/window/hooks/windows.hook.js +25 -0
  263. package/dist/window/host.d.ts +31 -0
  264. package/dist/window/host.d.ts.map +1 -0
  265. package/dist/window/host.js +270 -0
  266. package/dist/window/hub.d.ts +41 -0
  267. package/dist/window/hub.d.ts.map +1 -0
  268. package/dist/window/hub.js +100 -0
  269. package/dist/window/index.d.ts +15 -0
  270. package/dist/window/index.d.ts.map +1 -0
  271. package/dist/window/index.js +10 -0
  272. package/dist/window/lifecycle/mount.d.ts +47 -0
  273. package/dist/window/lifecycle/mount.d.ts.map +1 -0
  274. package/dist/window/lifecycle/mount.js +92 -0
  275. package/dist/window/lifecycle/states.d.ts +33 -0
  276. package/dist/window/lifecycle/states.d.ts.map +1 -0
  277. package/dist/window/lifecycle/states.js +7 -0
  278. package/dist/window/placement.d.ts +33 -0
  279. package/dist/window/placement.d.ts.map +1 -0
  280. package/dist/window/placement.js +57 -0
  281. package/dist/window/stage.d.ts +3 -0
  282. package/dist/window/stage.d.ts.map +1 -0
  283. package/dist/window/stage.js +41 -0
  284. package/package.json +59 -0
  285. package/src/application/apps/app-host.tsx +113 -0
  286. package/src/application/apps/app-logo.tsx +56 -0
  287. package/src/application/apps/apps.hook.ts +81 -0
  288. package/src/application/apps/open-app.hook.ts +18 -0
  289. package/src/application/index.ts +22 -0
  290. package/src/application/roles.hook.ts +41 -0
  291. package/src/application/spaces/current-space.hook.ts +43 -0
  292. package/src/application/spaces/current-space.store.ts +110 -0
  293. package/src/application/spaces/space.hook.ts +41 -0
  294. package/src/application/spaces/spaces.hook.ts +113 -0
  295. package/src/application/user/user.hook.ts +27 -0
  296. package/src/application/user/user.store.ts +85 -0
  297. package/src/auth/act-as.context.tsx +124 -0
  298. package/src/auth/auth.hook.ts +36 -0
  299. package/src/auth/can.hook.ts +149 -0
  300. package/src/auth/index.ts +6 -0
  301. package/src/auth/self.hook.ts +14 -0
  302. package/src/auth/session-kernel.ts +24 -0
  303. package/src/graph/action.ts +127 -0
  304. package/src/graph/async.ts +87 -0
  305. package/src/graph/call/call.hook.ts +42 -0
  306. package/src/graph/call/stream.hook.ts +200 -0
  307. package/src/graph/graph.context.ts +14 -0
  308. package/src/graph/guards.error.ts +58 -0
  309. package/src/graph/index.ts +20 -0
  310. package/src/graph/memory/base.ts +50 -0
  311. package/src/graph/memory/read/envelope.ts +98 -0
  312. package/src/graph/memory/read/idle.ts +24 -0
  313. package/src/graph/memory/read/read.ts +103 -0
  314. package/src/graph/memory/scope.ts +29 -0
  315. package/src/graph/mutate/move.hook.ts +70 -0
  316. package/src/graph/mutate/mutate.hook.ts +69 -0
  317. package/src/graph/query/children.hook.ts +136 -0
  318. package/src/graph/query/graph.hook.ts +13 -0
  319. package/src/graph/query/neighbors.hook.ts +124 -0
  320. package/src/graph/query/node.hook.ts +83 -0
  321. package/src/graph/query/query.hook.ts +42 -0
  322. package/src/index.ts +202 -0
  323. package/src/intents/envelope.ts +52 -0
  324. package/src/intents/index.ts +6 -0
  325. package/src/intents/listen-intent.hook.ts +92 -0
  326. package/src/intents/reply.ts +24 -0
  327. package/src/intents/send-intent.hook.ts +111 -0
  328. package/src/navigation/adapters/adapter.ts +13 -0
  329. package/src/navigation/adapters/memory.adapter.ts +22 -0
  330. package/src/navigation/adapters/url.adapter.ts +61 -0
  331. package/src/navigation/history.ts +170 -0
  332. package/src/navigation/hooks/ancestors.hook.ts +71 -0
  333. package/src/navigation/hooks/location.hook.ts +21 -0
  334. package/src/navigation/hooks/navigate.hook.ts +43 -0
  335. package/src/navigation/hooks/scope.hook.ts +34 -0
  336. package/src/navigation/index.ts +17 -0
  337. package/src/navigation/link.tsx +110 -0
  338. package/src/navigation/location.ts +122 -0
  339. package/src/navigation/scope.context.tsx +267 -0
  340. package/src/navigation/snapshot.ts +79 -0
  341. package/src/schema/bind-read.ts +319 -0
  342. package/src/schema/binding.ts +91 -0
  343. package/src/schema/domain.hook.ts +16 -0
  344. package/src/schema/function.hook.ts +68 -0
  345. package/src/schema/index.ts +25 -0
  346. package/src/schema/kernel.hook.ts +17 -0
  347. package/src/schema/method.hook.ts +103 -0
  348. package/src/schema/tag.ts +19 -0
  349. package/src/schema/types.ts +150 -0
  350. package/src/schema/write.ts +80 -0
  351. package/src/selection/index.ts +5 -0
  352. package/src/selection/live.ts +142 -0
  353. package/src/selection/scope.context.tsx +151 -0
  354. package/src/selection/selection.hook.ts +18 -0
  355. package/src/session/boot/boot.ts +57 -0
  356. package/src/session/boot/ownership.ts +54 -0
  357. package/src/session/boot/status.ts +32 -0
  358. package/src/session/connection/connection.hook.ts +16 -0
  359. package/src/session/connection/connection.store.ts +58 -0
  360. package/src/session/index.ts +8 -0
  361. package/src/session/kernel-client.hook.ts +13 -0
  362. package/src/session/kernel-instance.hook.ts +22 -0
  363. package/src/session/provider.tsx +205 -0
  364. package/src/session/session.context.ts +27 -0
  365. package/src/session/shell.hook.ts +19 -0
  366. package/src/window/adopt.ts +57 -0
  367. package/src/window/hooks/close-guard.hook.ts +100 -0
  368. package/src/window/hooks/target-node.hook.ts +94 -0
  369. package/src/window/hooks/views-for.hook.ts +82 -0
  370. package/src/window/hooks/window.hook.ts +18 -0
  371. package/src/window/hooks/windows.hook.ts +40 -0
  372. package/src/window/host.tsx +347 -0
  373. package/src/window/hub.ts +156 -0
  374. package/src/window/index.ts +17 -0
  375. package/src/window/lifecycle/mount.ts +134 -0
  376. package/src/window/lifecycle/states.ts +23 -0
  377. package/src/window/placement.ts +84 -0
  378. package/src/window/stage.tsx +46 -0
@@ -0,0 +1,92 @@
1
+ function isViewDescriptor(view) {
2
+ return typeof view === 'object' && view !== null && 'id' in view && 'path' in view;
3
+ }
4
+ /** Last path segment or host, alnum-only — a stable-ish functionId from a url. */
5
+ function slugOfUrl(url) {
6
+ const trimmed = url.replace(/[#?].*$/, '').replace(/\/+$/, '');
7
+ const tail = trimmed.split('/').filter(Boolean).pop() ?? 'view';
8
+ return tail.replace(/[^a-z0-9]/gi, '').slice(-32) || 'view';
9
+ }
10
+ /** A ref addressable by the kernel read surface (`get`). `@<id>` for bare ids. */
11
+ function toPath(ref) {
12
+ if (typeof ref === 'string') {
13
+ return ref.startsWith('/') || ref.startsWith('@') ? ref : `@${ref}`;
14
+ }
15
+ const obj = ref;
16
+ if (obj.path?.raw !== undefined)
17
+ return obj.path.raw;
18
+ if (obj.id !== undefined)
19
+ return `@${obj.id}`;
20
+ throw new Error('ViewHost: unresolvable view ref');
21
+ }
22
+ async function resolveViewRef(kernel, ref, handshakeHint) {
23
+ const node = await kernel.get(toPath(ref));
24
+ if (node === null)
25
+ throw new Error(`ViewHost: view not found (${toPath(ref)})`);
26
+ const props = node.props ?? {};
27
+ const url = props.url;
28
+ if (typeof url !== 'string' || url.length === 0) {
29
+ throw new Error('ViewHost: resolved view has no url');
30
+ }
31
+ const handshake = normalizeHandshake(handshakeHint ?? props.handshake);
32
+ const id = node.id ?? slugOfUrl(url);
33
+ return { url, handshake, functionId: id };
34
+ }
35
+ function normalizeHandshake(value) {
36
+ return value === 'none' ? 'none' : 'shell';
37
+ }
38
+ /** Resolve a source; may hit the kernel for a bare/urless View ref (G5). */
39
+ export async function resolveSource(kernel, source) {
40
+ if ('url' in source) {
41
+ return { url: source.url, handshake: 'none', functionId: slugOfUrl(source.url) };
42
+ }
43
+ const view = source.view;
44
+ if (isViewDescriptor(view)) {
45
+ if (view.url !== undefined && view.url.length > 0) {
46
+ return {
47
+ url: view.url,
48
+ handshake: normalizeHandshake(view.handshake),
49
+ functionId: view.id,
50
+ };
51
+ }
52
+ return resolveViewRef(kernel, view.id, view.handshake);
53
+ }
54
+ return resolveViewRef(kernel, view);
55
+ }
56
+ /** A stable remount key: the same source identity must not remount. */
57
+ export function sourceKey(source) {
58
+ if ('url' in source)
59
+ return `url:${source.url}`;
60
+ const view = source.view;
61
+ if (isViewDescriptor(view))
62
+ return `view:${view.id}:${view.url ?? ''}:${view.handshake ?? ''}`;
63
+ if (typeof view === 'string')
64
+ return `ref:${view}`;
65
+ const obj = view;
66
+ return `ref:${obj.id ?? obj.path?.raw ?? '?'}`;
67
+ }
68
+ /** The bare node id delivered as `targetNodeId` at mount / retarget. */
69
+ export function refToId(ref) {
70
+ if (ref === undefined)
71
+ return undefined;
72
+ if (typeof ref === 'string')
73
+ return ref.startsWith('@') ? ref.slice(1) : ref;
74
+ const obj = ref;
75
+ return obj.id;
76
+ }
77
+ /**
78
+ * Fallbackable = the view didn't speak the shell protocol (timeout/failed), not a
79
+ * security refusal (origin mismatch) — only the former downgrades to plain.
80
+ */
81
+ export function isFallbackableHandshakeError(err) {
82
+ const code = err?.code;
83
+ return code === 'HANDSHAKE_TIMEOUT' || code === 'HANDSHAKE_FAILED';
84
+ }
85
+ /** Parent → child hot-swap of the target node (no remount) — API §10 setTarget. */
86
+ export function sendSetTarget(shell, windowId, nodeId) {
87
+ shell.children.send(windowId, {
88
+ type: 'intent',
89
+ version: 1,
90
+ envelope: { name: 'setTarget', payload: { nodeId }, sender: { windowId: '<parent>' } },
91
+ });
92
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The WindowState phase machine (API §10) — the lifecycle of a hosted view as the
3
+ * React face observes it. Distinct from the shell's own `WindowState`
4
+ * (`active|hidden|closing|closed`): this is the phase a `ViewHost`/`AppHost`
5
+ * renders around.
6
+ */
7
+ /**
8
+ * Lifecycle of a hosted view as the React face observes it (API §10).
9
+ *
10
+ * `ready.downgraded` marks a mount that fell back to a plain iframe after a
11
+ * fallbackable handshake error. It rides here (not on the shell `Window`) until
12
+ * the shell `Window` grows a first-class flag — flagged in the module report.
13
+ *
14
+ * `crashed.windowId` is OPTIONAL: a crash before a window exists (a session-open
15
+ * failure, a pre-mount source-resolution failure) has no id to report — the type
16
+ * says so rather than carrying a placeholder `''`.
17
+ */
18
+ export type WindowState = {
19
+ readonly phase: 'mounting';
20
+ } | {
21
+ readonly phase: 'ready';
22
+ readonly windowId: string;
23
+ readonly downgraded?: boolean;
24
+ } | {
25
+ readonly phase: 'crashed';
26
+ readonly windowId?: string;
27
+ readonly error?: unknown;
28
+ } | {
29
+ readonly phase: 'closed';
30
+ readonly windowId: string;
31
+ readonly refusal?: string;
32
+ };
33
+ //# sourceMappingURL=states.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"states.d.ts","sourceRoot":"","sources":["../../../src/window/lifecycle/states.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,MAAM,WAAW,GACnB;IAAE,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;CAAE,GAC9B;IAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GACrF;IAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GACnF;IAAE,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The WindowState phase machine (API §10) — the lifecycle of a hosted view as the
3
+ * React face observes it. Distinct from the shell's own `WindowState`
4
+ * (`active|hidden|closing|closed`): this is the phase a `ViewHost`/`AppHost`
5
+ * renders around.
6
+ */
7
+ export {};
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Slot geometry (API §10) — the physical iframe-slot layer over a stage container
3
+ * (measure + project). Slots live in the stage (imperative DOM), insulated from
4
+ * React reconciliation of the hosts; only a slot's box is repositioned, so a
5
+ * healthy iframe never reloads on reorder/re-key/re-parent.
6
+ */
7
+ /** Viewport-space box a host projects onto its stage slot. */
8
+ export interface SlotRect {
9
+ readonly top: number;
10
+ readonly left: number;
11
+ readonly width: number;
12
+ readonly height: number;
13
+ }
14
+ /**
15
+ * One persistent slot inside the stage. Its `element` is the mount host handed
16
+ * to `shell.mount`; the element NEVER moves in the DOM while the owning host
17
+ * reorders/re-keys/re-parents — only its box is repositioned via `place`.
18
+ */
19
+ export interface StageSlot {
20
+ readonly element: HTMLElement;
21
+ place(rect: SlotRect): void;
22
+ release(): void;
23
+ }
24
+ export interface StageController {
25
+ acquire(): StageSlot;
26
+ dispose(): void;
27
+ }
28
+ /** Build the slot layer over a stage container element (imperative, no React). */
29
+ export declare function createStageController(container: HTMLElement): StageController;
30
+ /** Measure an anchor element's viewport box. */
31
+ export declare function rectOf(el: HTMLElement): SlotRect;
32
+ export declare function sameRect(a: SlotRect, b: SlotRect): boolean;
33
+ //# sourceMappingURL=placement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"placement.d.ts","sourceRoot":"","sources":["../../src/window/placement.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,8DAA8D;AAC9D,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAA;IAC7B,KAAK,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAA;IAC3B,OAAO,IAAI,IAAI,CAAA;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,IAAI,SAAS,CAAA;IACpB,OAAO,IAAI,IAAI,CAAA;CAChB;AAED,kFAAkF;AAClF,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,WAAW,GAAG,eAAe,CAyC7E;AAED,gDAAgD;AAChD,wBAAgB,MAAM,CAAC,EAAE,EAAE,WAAW,GAAG,QAAQ,CAGhD;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,CAE1D"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Slot geometry (API §10) — the physical iframe-slot layer over a stage container
3
+ * (measure + project). Slots live in the stage (imperative DOM), insulated from
4
+ * React reconciliation of the hosts; only a slot's box is repositioned, so a
5
+ * healthy iframe never reloads on reorder/re-key/re-parent.
6
+ */
7
+ /** Build the slot layer over a stage container element (imperative, no React). */
8
+ export function createStageController(container) {
9
+ const slots = new Set();
10
+ let disposed = false;
11
+ return {
12
+ acquire() {
13
+ const element = container.ownerDocument.createElement('div');
14
+ element.dataset.astraleViewSlot = '';
15
+ element.style.position = 'absolute';
16
+ element.style.top = '0';
17
+ element.style.left = '0';
18
+ element.style.pointerEvents = 'auto';
19
+ container.appendChild(element);
20
+ slots.add(element);
21
+ let released = false;
22
+ return {
23
+ element,
24
+ place(rect) {
25
+ // translate + explicit box, so a reflow of the container never drifts
26
+ // the slot off its anchor.
27
+ element.style.transform = `translate(${rect.left}px, ${rect.top}px)`;
28
+ element.style.width = `${rect.width}px`;
29
+ element.style.height = `${rect.height}px`;
30
+ },
31
+ release() {
32
+ if (released)
33
+ return;
34
+ released = true;
35
+ slots.delete(element);
36
+ element.remove();
37
+ },
38
+ };
39
+ },
40
+ dispose() {
41
+ if (disposed)
42
+ return;
43
+ disposed = true;
44
+ for (const el of slots)
45
+ el.remove();
46
+ slots.clear();
47
+ },
48
+ };
49
+ }
50
+ /** Measure an anchor element's viewport box. */
51
+ export function rectOf(el) {
52
+ const r = el.getBoundingClientRect();
53
+ return { top: r.top, left: r.left, width: r.width, height: r.height };
54
+ }
55
+ export function sameRect(a, b) {
56
+ return a.top === b.top && a.left === b.left && a.width === b.width && a.height === b.height;
57
+ }
@@ -0,0 +1,3 @@
1
+ import type { ReactElement } from 'react';
2
+ export declare function ViewStage(): ReactElement;
3
+ //# sourceMappingURL=stage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stage.d.ts","sourceRoot":"","sources":["../../src/window/stage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,YAAY,EAAE,MAAM,OAAO,CAAA;AA4BxD,wBAAgB,SAAS,IAAI,YAAY,CAiBxC"}
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * ViewStage (API §10) — the fixed-positioning layer that physically owns every
4
+ * iframe for the `ViewHost`/`AppHost`s in the tree. Mount it ONCE near the root.
5
+ * Reordering / re-parenting / re-keying hosts never reloads a healthy iframe:
6
+ * each iframe lives in a stage slot (imperative DOM), insulated from React
7
+ * reconciliation of the hosts; only the slot's box is repositioned.
8
+ */
9
+ import { useEffect, useMemo, useRef } from 'react';
10
+ import { useShell } from '../session/shell.hook';
11
+ import { getStageHub } from './hub';
12
+ import { createStageController } from './placement';
13
+ /**
14
+ * A full-viewport overlay that does not intercept pointer events — individual
15
+ * slots re-enable them, so only the iframes are interactive, not the gaps.
16
+ */
17
+ const STAGE_STYLE = {
18
+ position: 'fixed',
19
+ top: 0,
20
+ left: 0,
21
+ right: 0,
22
+ bottom: 0,
23
+ pointerEvents: 'none',
24
+ };
25
+ export function ViewStage() {
26
+ const shell = useShell();
27
+ const hub = useMemo(() => getStageHub(shell), [shell]);
28
+ const containerRef = useRef(null);
29
+ useEffect(() => {
30
+ const container = containerRef.current;
31
+ if (container === null)
32
+ return;
33
+ const controller = createStageController(container);
34
+ hub.setController(controller);
35
+ return () => {
36
+ hub.setController(null);
37
+ controller.dispose();
38
+ };
39
+ }, [hub]);
40
+ return _jsx("div", { ref: containerRef, "data-astrale-view-stage": "", style: STAGE_STYLE });
41
+ }
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@astrale-os/shell-react",
3
+ "version": "0.1.0",
4
+ "description": "Astrale shell-react — the React face of the shell (provider, hooks, view components)",
5
+ "license": "Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/astrale-os/shell.git",
9
+ "directory": "packages/shell-react"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "src"
14
+ ],
15
+ "type": "module",
16
+ "main": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js"
22
+ }
23
+ },
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "dependencies": {
28
+ "@astrale-domains/workspace-schema": ">=0.2.0 <1.0.0",
29
+ "@astrale-os/kernel-client": ">=0.3.0 <1.0.0",
30
+ "@astrale-os/kernel-core": ">=0.6.0 <1.0.0",
31
+ "@astrale-os/kernel-dsl": ">=0.1.0 <1.0.0"
32
+ },
33
+ "devDependencies": {
34
+ "@testing-library/dom": "^10.4.0",
35
+ "@testing-library/react": "^16.3.0",
36
+ "@types/react": "^19.2.0",
37
+ "@types/react-dom": "^19.2.0",
38
+ "@typescript/native-preview": "latest",
39
+ "@vitejs/plugin-react": "^5.0.4",
40
+ "happy-dom": "^20.0.0",
41
+ "react": "^19.2.0",
42
+ "react-dom": "^19.2.0",
43
+ "typescript": "~6.0.1-rc",
44
+ "vitest": "^3.2.4",
45
+ "@astrale-os/shell": "0.2.0"
46
+ },
47
+ "peerDependencies": {
48
+ "@astrale-os/shell": ">=0.2.0 <1.0.0",
49
+ "react": ">=18"
50
+ },
51
+ "scripts": {
52
+ "build": "tsgo",
53
+ "clean": "rm -rf dist *.tsbuildinfo",
54
+ "typecheck": "tsgo --noEmit && tsgo --noEmit -p tsconfig.test.json",
55
+ "test": "vitest run",
56
+ "test:watch": "vitest",
57
+ "preinstall": "node .check-workspace.cjs"
58
+ }
59
+ }
@@ -0,0 +1,113 @@
1
+ import type { NodeRefInput } from '@astrale-os/kernel-client'
2
+ import type { ApplicationNode, AppSession, IntentRegistry, SandboxProfile } from '@astrale-os/shell'
3
+ import type { CSSProperties, ReactElement, ReactNode } from 'react'
4
+
5
+ /**
6
+ * AppHost (API §12.2) = useOpenApp + ViewHost. Opens the app's `AppSession`,
7
+ * mounts its entry view with the session credential, and DISPOSES the session
8
+ * with the window (unmount, or an app change). `opening` renders while the
9
+ * session opens; `no-view` when the app pins no entrypoint; every mounted-frame
10
+ * phase is the window module's `WindowState`.
11
+ */
12
+ import { forwardRef, useEffect, useRef, useState } from 'react'
13
+
14
+ import type { ViewHostHandle } from '../../window/host'
15
+ import type { ViewDescriptor } from '../../window/lifecycle/mount'
16
+ import type { WindowState } from '../../window/lifecycle/states'
17
+
18
+ import { ViewHost } from '../../window/host'
19
+ import { toRef } from './apps.hook'
20
+ import { useOpenApp } from './open-app.hook'
21
+
22
+ /** The states `AppHost`'s fallback answers — the frame's, plus the two OS phases. */
23
+ export type AppHostFallbackState =
24
+ | WindowState
25
+ | { readonly phase: 'opening' }
26
+ | { readonly phase: 'no-view' }
27
+
28
+ export interface AppHostProps {
29
+ app: ApplicationNode | NodeRefInput
30
+ /**
31
+ * Default target node delivered to the app's view as `targetNodeId` (API §12.2).
32
+ * Container-anchored apps need one — the dock passes the current space. A deeper
33
+ * fix (an install-time app data container + `app::open` returning a default
34
+ * target) is a tracked domain-modeling question; until then the host supplies it.
35
+ */
36
+ node?: NodeRefInput
37
+ capabilities?: { intents: readonly (keyof IntentRegistry)[] }
38
+ sandbox?: SandboxProfile | null
39
+ fallback?: (state: AppHostFallbackState) => ReactNode
40
+ onSession?: (session: AppSession) => void
41
+ className?: string
42
+ style?: CSSProperties
43
+ }
44
+
45
+ export const AppHost = forwardRef<ViewHostHandle, AppHostProps>(
46
+ function AppHost(props, ref): ReactElement {
47
+ const open = useOpenApp()
48
+ const [session, setSession] = useState<AppSession | null>(null)
49
+ const [error, setError] = useState<unknown>(undefined)
50
+
51
+ const appKey = toRef(props.app)
52
+ const openRef = useRef(open)
53
+ openRef.current = open
54
+ const appRef = useRef(props.app)
55
+ appRef.current = props.app
56
+ const onSessionRef = useRef(props.onSession)
57
+ onSessionRef.current = props.onSession
58
+
59
+ // Open (re)opens the session per app; the cleanup disposes it — so the session
60
+ // is torn down with the window (unmount) and swapped on an app change.
61
+ useEffect(() => {
62
+ let cancelled = false
63
+ let opened: AppSession | null = null
64
+ setSession(null)
65
+ setError(undefined)
66
+ void openRef.current(appRef.current).then(
67
+ (s) => {
68
+ if (cancelled) {
69
+ s.dispose()
70
+ return
71
+ }
72
+ opened = s
73
+ setSession(s)
74
+ onSessionRef.current?.(s)
75
+ },
76
+ (e: unknown) => {
77
+ if (!cancelled) setError(e)
78
+ },
79
+ )
80
+ return () => {
81
+ cancelled = true
82
+ opened?.dispose()
83
+ }
84
+ // eslint-disable-next-line react-hooks/exhaustive-deps
85
+ }, [appKey])
86
+
87
+ const { fallback, className, style } = props
88
+ const box = (children: ReactNode): ReactElement => (
89
+ <div className={className} style={style}>
90
+ {children}
91
+ </div>
92
+ )
93
+
94
+ // A session-open failure has no window yet — `windowId` is honestly absent (§10).
95
+ if (error !== undefined) return box(fallback?.({ phase: 'crashed', error }) ?? null)
96
+ if (session === null) return box(fallback?.({ phase: 'opening' }) ?? null)
97
+ if (session.view === null) return box(fallback?.({ phase: 'no-view' }) ?? null)
98
+
99
+ return (
100
+ <ViewHost
101
+ ref={ref}
102
+ view={session.view as ViewDescriptor}
103
+ node={props.node}
104
+ credential={session.credential}
105
+ capabilities={props.capabilities ?? { intents: [] }}
106
+ sandbox={props.sandbox}
107
+ fallback={fallback ? (s: WindowState) => fallback(s) : undefined}
108
+ className={className}
109
+ style={style}
110
+ />
111
+ )
112
+ },
113
+ )
@@ -0,0 +1,56 @@
1
+ /**
2
+ * AppLogo (API §12.2) — render a node's logo prop: a url/`data:` URI as `<img>`,
3
+ * or an inline SVG string (sanitized + inlined), else `fallback`. The SVG path is
4
+ * a MINIMAL dependency-free strip (drops `<script>`/`<foreignObject>`, `on*`
5
+ * handlers, `javascript:` URLs); pass pre-sanitized markup for a hardened guard.
6
+ */
7
+ import type { CSSProperties, ReactElement, ReactNode } from 'react'
8
+
9
+ const URL_LOGO = /^(?:data:|https?:|\/)/i
10
+
11
+ export interface AppLogoProps {
12
+ /** The logo string — a url/`data:` URI or an inline `<svg>` string. */
13
+ logo?: string
14
+ /** `<img>` alt text (url logos only). Default: `''` (decorative). */
15
+ alt?: string
16
+ className?: string
17
+ style?: CSSProperties
18
+ /** Rendered when `logo` is absent or unusable (e.g. a letter avatar). */
19
+ fallback?: ReactNode
20
+ }
21
+
22
+ export function AppLogo({ logo, alt, className, style, fallback }: AppLogoProps): ReactElement {
23
+ if (logo !== undefined && URL_LOGO.test(logo)) {
24
+ return <img src={logo} alt={alt ?? ''} className={className} style={style} />
25
+ }
26
+ const svg = logo !== undefined ? sanitizeInlineSvg(logo) : null
27
+ if (svg !== null) {
28
+ return (
29
+ <span
30
+ aria-hidden
31
+ className={className}
32
+ style={style}
33
+ // Minimally sanitized inline SVG — see the module header for the trust boundary.
34
+ dangerouslySetInnerHTML={{ __html: svg }}
35
+ />
36
+ )
37
+ }
38
+ return <>{fallback ?? null}</>
39
+ }
40
+
41
+ /**
42
+ * Accept only a string that opens with `<svg`, and strip the obvious script
43
+ * vectors: `<script>`/`<foreignObject>` blocks, `on*=` handlers, `javascript:`
44
+ * URLs. Intentionally minimal (no heavy dep) — not a general HTML sanitizer.
45
+ */
46
+ function sanitizeInlineSvg(raw: string): string | null {
47
+ const s = raw.trimStart()
48
+ if (!s.toLowerCase().startsWith('<svg')) return null
49
+ return s
50
+ .replace(/<\s*script[\s\S]*?<\s*\/\s*script\s*>/gi, '')
51
+ .replace(/<\s*foreignObject[\s\S]*?<\s*\/\s*foreignObject\s*>/gi, '')
52
+ .replace(/\son[a-z0-9_-]+\s*=\s*"[^"]*"/gi, '')
53
+ .replace(/\son[a-z0-9_-]+\s*=\s*'[^']*'/gi, '')
54
+ .replace(/\son[a-z0-9_-]+\s*=\s*[^\s>]+/gi, '')
55
+ .replace(/(href|xlink:href|src)\s*=\s*(["'])\s*javascript:[^"']*\2/gi, '')
56
+ }
@@ -0,0 +1,81 @@
1
+ import type { NodeRefInput } from '@astrale-os/kernel-client'
2
+ import type { ApplicationNode, SpaceNode } from '@astrale-os/shell'
3
+
4
+ import { normalizeRef } from '@astrale-os/kernel-client'
5
+ /**
6
+ * useApps(space?) (API §12.2) — the apps installed in a space (`installed_in`
7
+ * in-edges). No arg targets the CURRENT space (shared store, so a switch anywhere
8
+ * swaps the dock) and is IDLE while none is selected (§1.3); an explicit `null`
9
+ * stays idle; OS plane unavailable → fully idle. Also `toRef`, the app/space
10
+ * subject normalizer the apps hooks share.
11
+ */
12
+ import { useCallback, useEffect, useMemo, useSyncExternalStore } from 'react'
13
+
14
+ import type { ReadState } from '../../graph/memory/read/envelope'
15
+ import type { CurrentSpaceSnapshot } from '../spaces/current-space.store'
16
+
17
+ import { useAsyncResource } from '../../graph/async'
18
+ import { useShell } from '../../session/shell.hook'
19
+ import { currentSpaceStore } from '../spaces/current-space.store'
20
+ import { isOsPlaneAvailable } from '../user/user.store'
21
+
22
+ const NO_APPS: readonly ApplicationNode[] = []
23
+ const IDLE_CURRENT: CurrentSpaceSnapshot = { space: null, pending: false, error: undefined }
24
+
25
+ export interface AppsResult extends ReadState {
26
+ readonly apps: readonly ApplicationNode[]
27
+ }
28
+
29
+ export function useApps(space?: SpaceNode | NodeRefInput | null): AppsResult {
30
+ const shell = useShell()
31
+ const available = isOsPlaneAvailable(shell)
32
+
33
+ // No arg → the CURRENT space, read through the shared store so a switch in any
34
+ // window swaps the dock here (§12.2). An explicit `null` stays idle; an explicit
35
+ // space bypasses the store entirely (no needless currentSpace fetch). OS plane
36
+ // unavailable (§12.2) → fully idle.
37
+ const useCurrent = space === undefined
38
+ const store = currentSpaceStore(shell)
39
+ useEffect(() => {
40
+ if (useCurrent && available) store.ensure()
41
+ }, [store, useCurrent, available])
42
+ const subscribe = useCallback(
43
+ (cb: () => void) => (useCurrent && available ? store.subscribe(cb) : () => {}),
44
+ [store, useCurrent, available],
45
+ )
46
+ const getSnapshot = useCallback(
47
+ (): CurrentSpaceSnapshot => (useCurrent && available ? store.getSnapshot() : IDLE_CURRENT),
48
+ [store, useCurrent, available],
49
+ )
50
+ const currentSnap = useSyncExternalStore(subscribe, getSnapshot, getSnapshot)
51
+
52
+ const effective = useCurrent ? currentSnap.space : (space ?? null)
53
+ const effRef = effective === null ? null : toRef(effective)
54
+ const appsRes = useAsyncResource<readonly ApplicationNode[]>(
55
+ !available || effRef === null ? null : () => shell.apps.in(effRef),
56
+ NO_APPS,
57
+ [shell, effRef, available],
58
+ )
59
+
60
+ return useMemo<AppsResult>(
61
+ () => ({
62
+ apps: appsRes.data,
63
+ pending: (useCurrent && currentSnap.pending) || appsRes.pending,
64
+ error: (useCurrent ? currentSnap.error : undefined) ?? appsRes.error,
65
+ live: false,
66
+ seq: 0,
67
+ refetch: appsRes.refetch,
68
+ }),
69
+ [appsRes.data, appsRes.pending, appsRes.error, appsRes.refetch, useCurrent, currentSnap],
70
+ )
71
+ }
72
+
73
+ /** A bound-node-ish subject carries a graph id. */
74
+ function hasId(x: unknown): x is { readonly id: string } {
75
+ return typeof x === 'object' && x !== null && 'id' in x
76
+ }
77
+
78
+ /** An `@id` ref for a bound node, else the normalized ref of a raw subject. */
79
+ export function toRef(x: NodeRefInput | { readonly id: string }): string {
80
+ return hasId(x) ? `@${x.id}` : normalizeRef(x)
81
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * useOpenApp (API §12.2) — the Action that opens an `AppSession` (the three-call
3
+ * app-open protocol lives in the shell package). Resolves to the session; disposal
4
+ * is the caller's (AppHost disposes it with the window).
5
+ */
6
+ import type { NodeRefInput } from '@astrale-os/kernel-client'
7
+ import type { ApplicationNode, AppSession } from '@astrale-os/shell'
8
+
9
+ import type { Action } from '../../graph/action'
10
+
11
+ import { useAction } from '../../graph/action'
12
+ import { useShell } from '../../session/shell.hook'
13
+ import { toRef } from './apps.hook'
14
+
15
+ export function useOpenApp(): Action<[ApplicationNode | NodeRefInput], AppSession> {
16
+ const shell = useShell()
17
+ return useAction((app: ApplicationNode | NodeRefInput) => shell.apps.open(toRef(app)))
18
+ }
@@ -0,0 +1,22 @@
1
+ // application — API §12.2. The OS-plane hooks over the shell package's
2
+ // `shell.user/spaces/apps` namespaces + the window module: useUser, useSpaces,
3
+ // useCurrentSpace, useSpace, useApps, useRoles, useOpenApp, AppHost, AppLogo.
4
+ export { useUser } from './user/user.hook'
5
+ export { useSpaces } from './spaces/spaces.hook'
6
+ export type { SpacesResult } from './spaces/spaces.hook'
7
+ export { useCurrentSpace } from './spaces/current-space.hook'
8
+ export type { CurrentSpaceResult } from './spaces/current-space.hook'
9
+ export { useSpace } from './spaces/space.hook'
10
+ export type { SpaceResult } from './spaces/space.hook'
11
+ export { useApps } from './apps/apps.hook'
12
+ export type { AppsResult } from './apps/apps.hook'
13
+ export { useOpenApp } from './apps/open-app.hook'
14
+ export { AppHost } from './apps/app-host'
15
+ export type { AppHostFallbackState, AppHostProps } from './apps/app-host'
16
+ export { AppLogo } from './apps/app-logo'
17
+ export type { AppLogoProps } from './apps/app-logo'
18
+ export { useRoles } from './roles.hook'
19
+ export type { RolesResult } from './roles.hook'
20
+
21
+ // The OS node/session types (from the shell package, single import surface).
22
+ export type { ApplicationNode, AppSession, RoleNode, SpaceNode, UserNode } from '@astrale-os/shell'