@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,82 @@
1
+ import type { PathLike } from '@astrale-os/kernel-client/graph'
2
+
3
+ import { K } from '@astrale-os/kernel-core'
4
+ /**
5
+ * useViewsFor (API §10) — "what Views can render this node", as mountable
6
+ * `ViewDescriptor`s for the open-with menu. Backed one-shot (`live:false`) by the
7
+ * kernel `View.resolve` syscall, which derives the node's class and returns the
8
+ * caller-visible Views (USE-visibility enforced kernel-side), each with mount url.
9
+ */
10
+ import { useMemo } from 'react'
11
+
12
+ import type { ReadState } from '../../graph/memory/read/envelope'
13
+ import type { Node, ViewDescriptor } from '../lifecycle/mount'
14
+
15
+ import { useAsyncResource } from '../../graph/async'
16
+ import { useKernelClient } from '../../session/kernel-client.hook'
17
+
18
+ export interface ViewsForResult extends ReadState {
19
+ readonly views: readonly ViewDescriptor[]
20
+ }
21
+
22
+ /** The `View.resolve` wire address — the qualified static-method slug from the schema. */
23
+ const VIEW_RESOLVE_METHOD = K.View.resolve.path.method.raw
24
+
25
+ /** The kernel `View.resolve` row shape (id/path/url/name?/handshake?/origin). */
26
+ interface ViewOut {
27
+ readonly id: string
28
+ readonly path: string
29
+ readonly url: string
30
+ readonly name?: string
31
+ readonly handshake?: 'shell' | 'none'
32
+ readonly origin: 'self' | 'class'
33
+ }
34
+
35
+ const NO_VIEWS: readonly ViewDescriptor[] = Object.freeze([])
36
+
37
+ export function useViewsFor(node: Node | PathLike | null | undefined): ViewsForResult {
38
+ const kernel = useKernelClient()
39
+ const target = resolvePath(node)
40
+
41
+ const res = useAsyncResource<readonly ViewDescriptor[]>(
42
+ target === null ? null : () => resolveViews(kernel, target),
43
+ NO_VIEWS,
44
+ [kernel, target],
45
+ )
46
+
47
+ return useMemo<ViewsForResult>(
48
+ () => ({
49
+ views: res.data,
50
+ pending: res.pending,
51
+ error: res.error,
52
+ live: false,
53
+ seq: 0,
54
+ refetch: res.refetch,
55
+ }),
56
+ [res.data, res.pending, res.error, res.refetch],
57
+ )
58
+ }
59
+
60
+ /** Dispatch `View.resolve` and project its rows to mountable `ViewDescriptor`s. */
61
+ async function resolveViews(
62
+ kernel: { call(method: string, params: unknown): Promise<unknown> },
63
+ node: string,
64
+ ): Promise<readonly ViewDescriptor[]> {
65
+ const rows = (await kernel.call(VIEW_RESOLVE_METHOD, { node })) as ViewOut[] | null | undefined
66
+ if (rows === null || rows === undefined || rows.length === 0) return NO_VIEWS
67
+ return rows.map((r) => ({ id: r.id, path: r.path, url: r.url, handshake: r.handshake }))
68
+ }
69
+
70
+ /** A path/`@id` the kernel read surface accepts, or `null` for an idle subject. */
71
+ function resolvePath(node: Node | PathLike | null | undefined): string | null {
72
+ if (node === null || node === undefined) return null
73
+ if (typeof node === 'string') {
74
+ if (node.length === 0) return null
75
+ return node.startsWith('/') || node.startsWith('@') ? node : `@${node}`
76
+ }
77
+ const obj = node as { path?: { raw?: unknown }; id?: unknown; raw?: unknown }
78
+ if (typeof obj.path?.raw === 'string') return obj.path.raw
79
+ if (typeof obj.id === 'string') return `@${obj.id}`
80
+ if (typeof obj.raw === 'string') return obj.raw
81
+ return null
82
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * useWindow(id) (API §10) — one window's live phase + shell `Window` (null id /
3
+ * unknown id → a stable "no window"), reactive over the per-shell hub registry.
4
+ */
5
+ import { useMemo, useSyncExternalStore } from 'react'
6
+
7
+ import type { WindowView } from '../hub'
8
+
9
+ import { useShell } from '../../session/shell.hook'
10
+ import { getStageHub } from '../hub'
11
+
12
+ /** One window's live phase + shell `Window` (null id / unknown id → no window). */
13
+ export function useWindow(windowId: string | null): WindowView {
14
+ const shell = useShell()
15
+ const hub = useMemo(() => getStageHub(shell), [shell])
16
+ const snapshot = useMemo(() => () => hub.registry.select(windowId), [hub, windowId])
17
+ return useSyncExternalStore(hub.registry.subscribe, snapshot, snapshot)
18
+ }
@@ -0,0 +1,40 @@
1
+ import type { Window } from '@astrale-os/shell'
2
+
3
+ /**
4
+ * useWindows / useWindowList (API §10) — reactive views over the windows a
5
+ * `ViewHost`/`AppHost` mounts through the per-shell hub. The shell exposes no
6
+ * window-change feed, so the hub registry (fed by the hosts) is the reactive
7
+ * source; windows mounted imperatively via `shell.mount` outside React are not
8
+ * tracked here — flagged in the module report as a shell foundation gap.
9
+ */
10
+ import { useMemo, useSyncExternalStore } from 'react'
11
+
12
+ import { useShell } from '../../session/shell.hook'
13
+ import { getStageHub } from '../hub'
14
+
15
+ /** Every host-mounted window in this shell's tree, keyed by windowId. Reactive. */
16
+ export function useWindows(): ReadonlyMap<string, Window> {
17
+ const shell = useShell()
18
+ const hub = useMemo(() => getStageHub(shell), [shell])
19
+ return useSyncExternalStore(
20
+ hub.registry.subscribe,
21
+ hub.registry.getSnapshot,
22
+ hub.registry.getSnapshot,
23
+ )
24
+ }
25
+
26
+ /** One open window as a list entry. */
27
+ export interface WindowListEntry {
28
+ readonly windowId: string
29
+ readonly window: Window
30
+ }
31
+
32
+ /**
33
+ * The open windows as a reactive, insertion-ordered list — the array form of
34
+ * `useWindows()`. The underlying map is referentially stable between mutations,
35
+ * so the derived array is too (no render churn).
36
+ */
37
+ export function useWindowList(): readonly WindowListEntry[] {
38
+ const windows = useWindows()
39
+ return useMemo(() => [...windows].map(([windowId, window]) => ({ windowId, window })), [windows])
40
+ }
@@ -0,0 +1,347 @@
1
+ import type { NodeRefInput } from '@astrale-os/kernel-client'
2
+ import type {
3
+ CloseResult,
4
+ IntentRegistry,
5
+ MountCredential,
6
+ MountParams,
7
+ SandboxProfile,
8
+ Window,
9
+ } from '@astrale-os/shell'
10
+ import type { CSSProperties, ReactElement, ReactNode } from 'react'
11
+
12
+ /**
13
+ * ViewHost (API §10) — hosts one view. It renders a normal-flow anchor and
14
+ * projects that box onto a stage slot (ResizeObserver); the iframe lives in the
15
+ * stage, so reorder/re-key never reloads a healthy frame. It drives the
16
+ * `WindowState` machine, owns teardown (unmount closes its window), survives
17
+ * StrictMode double-mount, and downgrades a fallbackable handshake to plain.
18
+ */
19
+ import {
20
+ forwardRef,
21
+ useCallback,
22
+ useEffect,
23
+ useImperativeHandle,
24
+ useLayoutEffect,
25
+ useMemo,
26
+ useRef,
27
+ useState,
28
+ } from 'react'
29
+
30
+ import type { AdoptRecord } from './adopt'
31
+ import type { ViewSource } from './lifecycle/mount'
32
+ import type { WindowState } from './lifecycle/states'
33
+ import type { SlotRect, StageController, StageSlot } from './placement'
34
+
35
+ import { useShell } from '../session/shell.hook'
36
+ import { getStageHub } from './hub'
37
+ import {
38
+ isFallbackableHandshakeError,
39
+ refToId,
40
+ resolveSource,
41
+ sendSetTarget,
42
+ sourceKey,
43
+ } from './lifecycle/mount'
44
+ import { rectOf, sameRect } from './placement'
45
+
46
+ /** Imperative handle on a mounted `ViewHost` (API §10). */
47
+ export interface ViewHostHandle {
48
+ readonly windowId: string | null
49
+ close(opts?: { force?: boolean; timeoutMs?: number }): Promise<CloseResult>
50
+ /** The one explicit remount. */
51
+ refresh(): void
52
+ }
53
+
54
+ export type ViewHostProps = ViewSource & {
55
+ /** Node this view opens on — delivered as `targetNodeId`; a change retargets. */
56
+ node?: NodeRefInput
57
+ capabilities?: { intents: readonly (keyof IntentRegistry)[] }
58
+ sandbox?: SandboxProfile | null
59
+ credential?: MountCredential
60
+ /** Rendered inside the anchor while the frame is not `ready`. */
61
+ fallback?: (state: WindowState) => ReactNode
62
+ onStateChange?: (state: WindowState) => void
63
+ className?: string
64
+ style?: CSSProperties
65
+ }
66
+
67
+ const MOUNTING: WindowState = { phase: 'mounting' }
68
+
69
+ /** The live window a host owns — enough to hand off (adopt) across a remount. */
70
+ interface LiveWindow {
71
+ readonly windowId: string
72
+ readonly window: Window
73
+ readonly state: WindowState
74
+ readonly targetId: string | undefined
75
+ }
76
+
77
+ export const ViewHost = forwardRef<ViewHostHandle, ViewHostProps>(
78
+ function ViewHost(props, ref): ReactElement {
79
+ const shell = useShell()
80
+ const hub = useMemo(() => getStageHub(shell), [shell])
81
+
82
+ const anchorRef = useRef<HTMLDivElement | null>(null)
83
+ const slotRef = useRef<StageSlot | null>(null)
84
+ const windowIdRef = useRef<string | null>(null)
85
+ const aliveRef = useRef(true)
86
+ // The live window (set at ready / on adopt, cleared on close) — the unit an
87
+ // unmount stashes for a re-key/re-parent remount to adopt (§10).
88
+ const liveRef = useRef<LiveWindow | null>(null)
89
+ // Set by `refresh()`: the next unmount CLOSES (real remount) instead of stashing.
90
+ const refreshingRef = useRef(false)
91
+ // Last placed slot rect — reset when the slot changes; guards redundant places.
92
+ const lastRectRef = useRef<SlotRect | null>(null)
93
+
94
+ const propsRef = useRef(props)
95
+ propsRef.current = props
96
+
97
+ const [state, setState] = useState<WindowState>(MOUNTING)
98
+ const [refreshNonce, setRefreshNonce] = useState(0)
99
+
100
+ const mountKey = sourceKey(props)
101
+ const targetId = refToId(props.node)
102
+ const lastTargetRef = useRef<string | undefined>(undefined)
103
+
104
+ const setStateSafe = (next: WindowState) => {
105
+ if (!aliveRef.current) return
106
+ setState(next)
107
+ propsRef.current.onStateChange?.(next)
108
+ }
109
+
110
+ useEffect(() => {
111
+ aliveRef.current = true
112
+ return () => {
113
+ aliveRef.current = false
114
+ }
115
+ }, [])
116
+
117
+ useImperativeHandle(
118
+ ref,
119
+ (): ViewHostHandle => ({
120
+ get windowId() {
121
+ return windowIdRef.current
122
+ },
123
+ async close(opts): Promise<CloseResult> {
124
+ const id = windowIdRef.current
125
+ if (id === null) return { kind: 'noop' }
126
+ const result = await shell.close(id, opts)
127
+ if (result.kind === 'closed') {
128
+ windowIdRef.current = null
129
+ liveRef.current = null
130
+ hub.registry.remove(id)
131
+ setStateSafe({ phase: 'closed', windowId: id })
132
+ }
133
+ return result
134
+ },
135
+ refresh() {
136
+ // A real remount: the next unmount closes (not stashes) so the frame reloads.
137
+ refreshingRef.current = true
138
+ setRefreshNonce((n) => n + 1)
139
+ },
140
+ }),
141
+ [shell, hub],
142
+ )
143
+
144
+ // ── Placement: project the anchor box onto the current stage slot. Guarded by
145
+ // rect-equality, so it is cheap to call on every commit (a sibling reorder
146
+ // moves the anchor with no resize/scroll event — API §10).
147
+ const measure = useCallback((): void => {
148
+ const slot = slotRef.current
149
+ const anchor = anchorRef.current
150
+ if (slot === null || anchor === null) return
151
+ const rect = rectOf(anchor)
152
+ const last = lastRectRef.current
153
+ if (last !== null && sameRect(last, rect)) return
154
+ lastRectRef.current = rect
155
+ slot.place(rect)
156
+ }, [])
157
+
158
+ // ── Mount lifecycle. Keyed on the source identity + refresh nonce only — a
159
+ // node (targetNodeId) change retargets via setTarget, never a remount. A
160
+ // re-key/re-parent unmount+remount ADOPTS the live window through the hub
161
+ // (deferred close), so a healthy iframe is never reloaded (§10).
162
+ useEffect(() => {
163
+ let cancelled = false
164
+ let controllerWaiter: (() => void) | null = null
165
+ // Consume the refresh flag: this setup is the (real) remount a refresh asked
166
+ // for, so it must NOT adopt the window its own cleanup just closed.
167
+ const wasRefresh = refreshingRef.current
168
+ refreshingRef.current = false
169
+
170
+ /** Close the window, release its slot, drop it from the registry (unadopted). */
171
+ function closeAndRelease(id: string, slot: StageSlot): void {
172
+ void shell
173
+ .close(id, { force: false })
174
+ .then((r) => (r.kind === 'closed' ? undefined : shell.close(id, { force: true })))
175
+ .catch(() => {})
176
+ .finally(() => hub.registry.remove(id))
177
+ slot.release()
178
+ }
179
+
180
+ /** Stash the live window for adoption, or (on refresh) close it outright. */
181
+ function cleanup(): void {
182
+ cancelled = true
183
+ controllerWaiter?.()
184
+ const live = liveRef.current
185
+ const slot = slotRef.current
186
+ slotRef.current = null
187
+ windowIdRef.current = null
188
+ liveRef.current = null
189
+ lastRectRef.current = null
190
+ if (live === null || slot === null) {
191
+ // Mount never resolved: release the slot; an in-flight mount self-closes.
192
+ slot?.release()
193
+ return
194
+ }
195
+ const record: AdoptRecord = {
196
+ windowId: live.windowId,
197
+ window: live.window,
198
+ slot,
199
+ state: live.state,
200
+ targetId: live.targetId,
201
+ dispose: () => closeAndRelease(live.windowId, slot),
202
+ }
203
+ if (refreshingRef.current) record.dispose()
204
+ else hub.stashForAdopt(mountKey, record)
205
+ }
206
+
207
+ // Adopt a live window a just-unmounted same-source host left behind.
208
+ if (!wasRefresh) {
209
+ const adopted = hub.takeAdopt(mountKey)
210
+ if (adopted !== null) {
211
+ slotRef.current = adopted.slot
212
+ windowIdRef.current = adopted.windowId
213
+ liveRef.current = {
214
+ windowId: adopted.windowId,
215
+ window: adopted.window,
216
+ state: adopted.state,
217
+ targetId: adopted.targetId,
218
+ }
219
+ lastTargetRef.current = adopted.targetId
220
+ hub.registry.upsert(adopted.windowId, adopted.window, adopted.state)
221
+ lastRectRef.current = null
222
+ measure() // reposition the surviving slot onto this host's anchor
223
+ setStateSafe(adopted.state)
224
+ return cleanup
225
+ }
226
+ }
227
+
228
+ async function mountWith(handshake: 'shell' | 'none', downgraded: boolean): Promise<void> {
229
+ const p = propsRef.current
230
+ let resolved
231
+ try {
232
+ resolved = await resolveSource(shell.kernel, sourceOf(p))
233
+ } catch (err) {
234
+ // No window yet (source resolution failed) — windowId honestly absent.
235
+ if (!cancelled) setStateSafe({ phase: 'crashed', error: err })
236
+ return
237
+ }
238
+ if (cancelled) return
239
+
240
+ const usedHandshake = handshake === 'none' ? 'none' : resolved.handshake
241
+ const deliveredTarget = refToId(p.node)
242
+ const params: MountParams = {
243
+ host: slotRef.current!.element,
244
+ url: resolved.url,
245
+ functionId: resolved.functionId,
246
+ capabilities: { intents: p.capabilities?.intents ?? [] },
247
+ targetNodeId: deliveredTarget,
248
+ sandbox: p.sandbox,
249
+ handshake: usedHandshake,
250
+ credential: p.credential,
251
+ }
252
+
253
+ try {
254
+ const win = await shell.mount(params)
255
+ // A StrictMode cleanup (or refresh) fired before mount resolved: adopt
256
+ // then immediately close so no orphan window survives.
257
+ if (cancelled) {
258
+ void win.close({ force: true }).catch(() => {})
259
+ return
260
+ }
261
+ windowIdRef.current = win.windowId
262
+ lastTargetRef.current = deliveredTarget
263
+ const ready: WindowState = downgraded
264
+ ? { phase: 'ready', windowId: win.windowId, downgraded: true }
265
+ : { phase: 'ready', windowId: win.windowId }
266
+ liveRef.current = {
267
+ windowId: win.windowId,
268
+ window: win.window,
269
+ state: ready,
270
+ targetId: deliveredTarget,
271
+ }
272
+ hub.registry.upsert(win.windowId, win.window, ready)
273
+ setStateSafe(ready)
274
+ } catch (err) {
275
+ if (cancelled) return
276
+ if (usedHandshake === 'shell' && isFallbackableHandshakeError(err)) {
277
+ await mountWith('none', true)
278
+ return
279
+ }
280
+ setStateSafe({ phase: 'crashed', windowId: windowIdRef.current ?? undefined, error: err })
281
+ }
282
+ }
283
+
284
+ function begin(controller: StageController): void {
285
+ if (cancelled || slotRef.current !== null) return
286
+ const slot = controller.acquire()
287
+ slotRef.current = slot
288
+ lastRectRef.current = null
289
+ measure()
290
+ setStateSafe(MOUNTING)
291
+ void mountWith('shell', false)
292
+ }
293
+
294
+ const existing = hub.getController()
295
+ if (existing !== null) begin(existing)
296
+ else {
297
+ controllerWaiter = hub.onController((c) => {
298
+ if (c !== null) begin(c)
299
+ })
300
+ }
301
+
302
+ return cleanup
303
+ // eslint-disable-next-line react-hooks/exhaustive-deps
304
+ }, [hub, shell, mountKey, refreshNonce])
305
+
306
+ // ── Retarget: a node change after ready hot-swaps via setTarget (no remount).
307
+ useEffect(() => {
308
+ if (state.phase !== 'ready') return
309
+ if (targetId === lastTargetRef.current) return
310
+ lastTargetRef.current = targetId
311
+ if (targetId === undefined) return
312
+ sendSetTarget(shell, state.windowId, targetId)
313
+ }, [shell, state, targetId])
314
+
315
+ // ── Placement observers: reposition on resize/scroll/anchor-resize.
316
+ useLayoutEffect(() => {
317
+ measure()
318
+ const anchor = anchorRef.current
319
+ const ro = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(measure) : null
320
+ if (anchor !== null) ro?.observe(anchor)
321
+ window.addEventListener('resize', measure)
322
+ window.addEventListener('scroll', measure, true)
323
+ return () => {
324
+ ro?.disconnect()
325
+ window.removeEventListener('resize', measure)
326
+ window.removeEventListener('scroll', measure, true)
327
+ }
328
+ }, [measure])
329
+
330
+ // Re-measure on EVERY commit (rect-equality-guarded): a sibling reorder moves
331
+ // the anchor with no resize/scroll event, so the slot must chase it here (§10).
332
+ useLayoutEffect(() => {
333
+ measure()
334
+ })
335
+
336
+ return (
337
+ <div ref={anchorRef} className={props.className} style={props.style}>
338
+ {state.phase !== 'ready' ? props.fallback?.(state) : null}
339
+ </div>
340
+ )
341
+ },
342
+ )
343
+
344
+ /** Narrow props back to the bare `ViewSource` discriminated union. */
345
+ function sourceOf(props: ViewHostProps): ViewSource {
346
+ return 'url' in props ? { url: props.url } : { view: props.view }
347
+ }
@@ -0,0 +1,156 @@
1
+ /**
2
+ * The per-shell windows hub (API §10). `ViewStage` mounts once near the root while
3
+ * `ViewHost`s live anywhere, so they rendezvous through a hub keyed by the shared
4
+ * `Shell` instance (a `WeakMap`), not context. It owns the `WindowRegistry`
5
+ * (reactive store behind `useWindows`/`useWindow`, fed by the hosts), the stage
6
+ * controller (the slot layer, published by the stage), and the adopt stash.
7
+ */
8
+ import type { Shell, Window } from '@astrale-os/shell'
9
+
10
+ import type { AdoptRecord } from './adopt'
11
+ import type { WindowState } from './lifecycle/states'
12
+ import type { StageController } from './placement'
13
+
14
+ import { createAdoptStash } from './adopt'
15
+
16
+ // ── Window registry (reactive store behind useWindows/useWindow) ─────────────
17
+
18
+ /** A host-tracked window: the shell `Window` plus the phase the host observes. */
19
+ interface WindowEntry {
20
+ readonly window: Window
21
+ readonly state: WindowState
22
+ }
23
+
24
+ /** A `useWindow` projection — a window's phase plus its shell `Window` (or none). */
25
+ export type WindowView = WindowState & { readonly window: Window | null }
26
+
27
+ export interface WindowRegistry {
28
+ /** Referentially stable between mutations (useSyncExternalStore contract). */
29
+ getSnapshot(): ReadonlyMap<string, Window>
30
+ /** Stable projection for one window id (null id → a stable "no window"). */
31
+ select(windowId: string | null): WindowView
32
+ subscribe(onChange: () => void): () => void
33
+ /** Host writes: register/transition/drop a window. */
34
+ upsert(windowId: string, window: Window, state: WindowState): void
35
+ patch(windowId: string, state: WindowState): void
36
+ remove(windowId: string): void
37
+ }
38
+
39
+ const NO_WINDOW: WindowView = { phase: 'closed', windowId: '', window: null }
40
+
41
+ function createWindowRegistry(): WindowRegistry {
42
+ const entries = new Map<string, WindowEntry>()
43
+ const listeners = new Set<() => void>()
44
+
45
+ // Cached snapshots: rebuilt only on mutation, so repeated getSnapshot/select
46
+ // calls between changes return identical references (no render loop).
47
+ let windowsSnapshot: ReadonlyMap<string, Window> | null = null
48
+ let selectCache = new Map<string, WindowView>()
49
+
50
+ function invalidate() {
51
+ windowsSnapshot = null
52
+ selectCache = new Map()
53
+ for (const l of listeners) l()
54
+ }
55
+
56
+ return {
57
+ getSnapshot() {
58
+ if (windowsSnapshot === null) {
59
+ const map = new Map<string, Window>()
60
+ for (const [id, entry] of entries) map.set(id, entry.window)
61
+ windowsSnapshot = map
62
+ }
63
+ return windowsSnapshot
64
+ },
65
+
66
+ select(windowId) {
67
+ if (windowId === null) return NO_WINDOW
68
+ const cached = selectCache.get(windowId)
69
+ if (cached !== undefined) return cached
70
+ const entry = entries.get(windowId)
71
+ const view: WindowView = entry
72
+ ? { ...entry.state, window: entry.window }
73
+ : { phase: 'closed', windowId, window: null }
74
+ selectCache.set(windowId, view)
75
+ return view
76
+ },
77
+
78
+ subscribe(onChange) {
79
+ listeners.add(onChange)
80
+ return () => {
81
+ listeners.delete(onChange)
82
+ }
83
+ },
84
+
85
+ upsert(windowId, window, state) {
86
+ entries.set(windowId, { window, state })
87
+ invalidate()
88
+ },
89
+
90
+ patch(windowId, state) {
91
+ const entry = entries.get(windowId)
92
+ if (entry === undefined) return
93
+ entries.set(windowId, { window: entry.window, state })
94
+ invalidate()
95
+ },
96
+
97
+ remove(windowId) {
98
+ if (!entries.delete(windowId)) return
99
+ invalidate()
100
+ },
101
+ }
102
+ }
103
+
104
+ // ── Per-shell hub ────────────────────────────────────────────────────────────
105
+
106
+ export interface StageHub {
107
+ readonly registry: WindowRegistry
108
+ getController(): StageController | null
109
+ /** Publish (stage mount) or retract (stage unmount) the slot layer. */
110
+ setController(controller: StageController | null): void
111
+ /** Await the controller — fires immediately if one is already published. */
112
+ onController(cb: (controller: StageController | null) => void): () => void
113
+ /** Stash an unmounting host's live window; disposes after one macrotask if unadopted. */
114
+ stashForAdopt(key: string, record: AdoptRecord): void
115
+ /** Reclaim a stashed window for a remount of the same source, cancelling its dispose. */
116
+ takeAdopt(key: string): AdoptRecord | null
117
+ }
118
+
119
+ const HUBS = new WeakMap<Shell, StageHub>()
120
+
121
+ /** The windows hub for a shell — one per shell object, created on first touch. */
122
+ export function getStageHub(shell: Shell): StageHub {
123
+ const existing = HUBS.get(shell)
124
+ if (existing !== undefined) return existing
125
+
126
+ const registry = createWindowRegistry()
127
+ let controller: StageController | null = null
128
+ const waiters = new Set<(c: StageController | null) => void>()
129
+ const adopt = createAdoptStash()
130
+
131
+ const hub: StageHub = {
132
+ registry,
133
+ getController() {
134
+ return controller
135
+ },
136
+ setController(next) {
137
+ controller = next
138
+ for (const w of waiters) w(next)
139
+ },
140
+ onController(cb) {
141
+ waiters.add(cb)
142
+ if (controller !== null) cb(controller)
143
+ return () => {
144
+ waiters.delete(cb)
145
+ }
146
+ },
147
+ stashForAdopt(key, record) {
148
+ adopt.stash(key, record)
149
+ },
150
+ takeAdopt(key) {
151
+ return adopt.take(key)
152
+ },
153
+ }
154
+ HUBS.set(shell, hub)
155
+ return hub
156
+ }
@@ -0,0 +1,17 @@
1
+ // window — API §10. ViewStage/ViewHost (the physical iframe-slot layer + a host
2
+ // that projects placement into a slot), the reactive window hooks, the child-side
3
+ // target-node + close-guard, and the (G5) view-resolution hook.
4
+ export { ViewStage } from './stage'
5
+ export { ViewHost } from './host'
6
+ export type { ViewHostProps, ViewHostHandle } from './host'
7
+ export { useWindows, useWindowList } from './hooks/windows.hook'
8
+ export type { WindowListEntry } from './hooks/windows.hook'
9
+ export { useWindow } from './hooks/window.hook'
10
+ export type { WindowView } from './hub'
11
+ export { useViewsFor } from './hooks/views-for.hook'
12
+ export type { ViewsForResult } from './hooks/views-for.hook'
13
+ export { useTargetNode } from './hooks/target-node.hook'
14
+ export { useCloseGuard } from './hooks/close-guard.hook'
15
+ export type { CloseGuard, CloseVerdict } from './hooks/close-guard.hook'
16
+ export type { ViewDescriptor, ViewSource, Node } from './lifecycle/mount'
17
+ export type { WindowState } from './lifecycle/states'