@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,124 @@
1
+ import type { AnyBoundNode, EdgeNames } from '@astrale-os/kernel-client'
2
+ import type { Schema } from '@astrale-os/kernel-dsl'
3
+
4
+ /**
5
+ * useOut / useIn (API §6) — the public typed traversal read hooks. Resolve
6
+ * endpoints one-shot through `BoundNode.out/in` (`live: false`); become live with
7
+ * no surface change once edge-endpoint ids land. Deviations tracked in API §6/G6.
8
+ */
9
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
10
+
11
+ import type { EndpointResult } from '../../schema/types'
12
+ import type { ReadState, ReadOptions } from '../memory/read/envelope'
13
+
14
+ import { schemaOf, tagSchema } from '../../schema/tag'
15
+ import { isIdleInput } from '../memory/read/idle'
16
+
17
+ export function useOut<S extends Schema, E extends EdgeNames<S>>(
18
+ node: AnyBoundNode<S> | null | undefined,
19
+ edge: E,
20
+ opts?: ReadOptions,
21
+ ): EndpointResult<S, E, 'to'> {
22
+ return useEndpoint(node, edge, 'out', opts) as unknown as EndpointResult<S, E, 'to'>
23
+ }
24
+
25
+ export function useIn<S extends Schema, E extends EdgeNames<S>>(
26
+ node: AnyBoundNode<S> | null | undefined,
27
+ edge: E,
28
+ opts?: ReadOptions,
29
+ ): EndpointResult<S, E, 'from'> {
30
+ return useEndpoint(node, edge, 'in', opts) as unknown as EndpointResult<S, E, 'from'>
31
+ }
32
+
33
+ type EndpointEnvelope<S extends Schema> = ReadState & {
34
+ readonly node?: AnyBoundNode<S> | null
35
+ readonly nodes?: readonly AnyBoundNode<S>[]
36
+ }
37
+
38
+ interface Traversal {
39
+ out(edge: string): Promise<unknown>
40
+ in(edge: string): Promise<unknown>
41
+ }
42
+
43
+ function useEndpoint<S extends Schema>(
44
+ node: AnyBoundNode<S> | null | undefined,
45
+ edge: string,
46
+ side: 'out' | 'in',
47
+ _opts?: ReadOptions,
48
+ ): EndpointEnvelope<S> {
49
+ const schema = isIdleInput(node) ? undefined : schemaOf(node)
50
+ const run = useCallback(async (): Promise<unknown> => {
51
+ if (isIdleInput(node)) return null
52
+ // AnyBoundNode omits traversal at the type level; the runtime node (bindAny
53
+ // → createBoundNode) carries out/in. The edge name is typed by the caller.
54
+ const t = node as unknown as Traversal
55
+ const result = await (side === 'out' ? t.out(edge) : t.in(edge))
56
+ if (schema !== undefined) tagEndpoints(result, schema)
57
+ return result
58
+ // eslint-disable-next-line react-hooks/exhaustive-deps
59
+ }, [node?.id, edge, side])
60
+
61
+ const { data, pending, error, refetch } = useAsyncRead(isIdleInput(node) ? null : run, [
62
+ node?.id ?? null,
63
+ edge,
64
+ side,
65
+ ])
66
+
67
+ return useMemo<EndpointEnvelope<S>>(() => {
68
+ const live: ReadState = { pending, error, live: false, seq: 0, refetch }
69
+ if (isIdleInput(node)) return { ...live, node: null, nodes: EMPTY }
70
+ if (Array.isArray(data)) return { ...live, nodes: data as readonly AnyBoundNode<S>[] }
71
+ return { ...live, node: (data ?? null) as AnyBoundNode<S> | null }
72
+ }, [node, data, pending, error, refetch])
73
+ }
74
+
75
+ // ── helpers ──────────────────────────────────────────────────────────────────
76
+
77
+ const EMPTY: readonly never[] = Object.freeze([])
78
+
79
+ function tagEndpoints(result: unknown, schema: Schema): void {
80
+ if (Array.isArray(result)) for (const r of result) tagSchema(r, schema)
81
+ else if (result !== null && result !== undefined) tagSchema(result, schema)
82
+ }
83
+
84
+ interface AsyncState<T> {
85
+ data?: T
86
+ pending: boolean
87
+ error: unknown
88
+ }
89
+
90
+ /** One-shot async read with pending/error/refetch and stale-response guarding. */
91
+ function useAsyncRead<T>(
92
+ run: (() => Promise<T>) | null,
93
+ deps: readonly unknown[],
94
+ ): AsyncState<T> & { refetch(): Promise<void> } {
95
+ const [state, setState] = useState<AsyncState<T>>({ pending: run !== null, error: undefined })
96
+ const runRef = useRef(run)
97
+ runRef.current = run
98
+ const seq = useRef(0)
99
+
100
+ const refetch = useCallback((): Promise<void> => {
101
+ const current = runRef.current
102
+ if (current === null) {
103
+ setState({ pending: false, error: undefined, data: undefined })
104
+ return Promise.resolve()
105
+ }
106
+ const my = ++seq.current
107
+ setState((s) => ({ ...s, pending: true, error: undefined }))
108
+ return current().then(
109
+ (data) => {
110
+ if (my === seq.current) setState({ data, pending: false, error: undefined })
111
+ },
112
+ (error: unknown) => {
113
+ if (my === seq.current) setState((s) => ({ data: s.data, pending: false, error }))
114
+ },
115
+ )
116
+ }, [])
117
+
118
+ useEffect(() => {
119
+ void refetch()
120
+ // eslint-disable-next-line react-hooks/exhaustive-deps
121
+ }, deps)
122
+
123
+ return { ...state, refetch }
124
+ }
@@ -0,0 +1,83 @@
1
+ import type { NodeRefInput, TypeNames } from '@astrale-os/kernel-client'
2
+ import type { PathLike } from '@astrale-os/kernel-client/graph'
3
+ import type { Schema } from '@astrale-os/kernel-dsl'
4
+
5
+ /**
6
+ * useNode (API §5 + §6) — ONE public symbol, overloaded: pass a `Schema` (+ type)
7
+ * for the typed `BoundNode` result, or a bare ref for the untyped `Node` result.
8
+ * Both forms ride the SAME memory shape — a typed read and its untyped twin are
9
+ * one cache entry, one liveness, one overlay. The runtime dispatches on the first
10
+ * argument; a given call site is always one form, so hook order is stable.
11
+ */
12
+ import { useMemo } from 'react'
13
+
14
+ import type { TypedNodeResult } from '../../schema/types'
15
+ import type { ReadState, Node, ReadOptions } from '../memory/read/envelope'
16
+
17
+ import { useNode as useNodeTyped } from '../../schema/bind-read'
18
+ import { useRead } from '../memory/read/read'
19
+
20
+ export interface NodeResult extends ReadState {
21
+ /** The node, or `null` when missing ≡ masked (never throws NOT_FOUND). */
22
+ readonly node: Node | null
23
+ }
24
+
25
+ /**
26
+ * A `Schema` (from `defineSchema`) — its shape (`domain`/`classes`/`interfaces`)
27
+ * is disjoint from a `PathLike` (`string | Path`), so this cleanly routes the
28
+ * typed vs untyped overload at runtime.
29
+ */
30
+ function isSchema(value: unknown): value is Schema {
31
+ return (
32
+ typeof value === 'object' &&
33
+ value !== null &&
34
+ 'domain' in value &&
35
+ 'classes' in value &&
36
+ 'interfaces' in value
37
+ )
38
+ }
39
+
40
+ /** One node by path — live, suspense-first; `null` ref is idle (§1.3). */
41
+ function useNodeUntyped(ref: PathLike | null | undefined, opts?: ReadOptions): NodeResult {
42
+ const r = useRead(ref, opts)
43
+ return useMemo(
44
+ () => ({
45
+ pending: r.pending,
46
+ error: r.error,
47
+ live: r.live,
48
+ seq: r.seq,
49
+ refetch: r.refetch,
50
+ node: r.first,
51
+ }),
52
+ [r],
53
+ )
54
+ }
55
+
56
+ /** Typed: one node bound under `schema` as `type`. */
57
+ export function useNode<S extends Schema, T extends TypeNames<S>>(
58
+ schema: S,
59
+ type: T,
60
+ ref: NodeRefInput | null | undefined,
61
+ opts?: ReadOptions,
62
+ ): TypedNodeResult<S, T>
63
+ /** Untyped: one node by ref — `null` ref is idle (§1.3). */
64
+ export function useNode(ref: PathLike | null | undefined, opts?: ReadOptions): NodeResult
65
+ // The implementation signature returns an erased supertype of both result
66
+ // shapes — every overload above is assignable to it regardless of `BoundNode`
67
+ // variance; callers only ever see the precise overload return types.
68
+ export function useNode(
69
+ a: unknown,
70
+ b?: unknown,
71
+ c?: unknown,
72
+ d?: unknown,
73
+ ): ReadState & { readonly node: unknown } {
74
+ if (isSchema(a)) {
75
+ return useNodeTyped(
76
+ a,
77
+ b as string,
78
+ c as NodeRefInput | null | undefined,
79
+ d as ReadOptions | undefined,
80
+ )
81
+ }
82
+ return useNodeUntyped(a as PathLike | null | undefined, b as ReadOptions | undefined)
83
+ }
@@ -0,0 +1,42 @@
1
+ import type { GatherBuilder, QueryASTInput, QuerySource } from '@astrale-os/kernel-client/graph'
2
+
3
+ /**
4
+ * useQuery (API §5) — a structured read: a builder callback or an AST value;
5
+ * `null` is idle (§1.3). Rides the §1 envelope over the shared memory, projecting
6
+ * roots/first/nodes and the gathered-alias arms.
7
+ */
8
+ import { useMemo } from 'react'
9
+
10
+ import type { ReadState, Node, ReadOptions, Windowed } from '../memory/read/envelope'
11
+
12
+ import { useRead } from '../memory/read/read'
13
+
14
+ export interface QueryResult extends ReadState, Windowed {
15
+ readonly nodes: readonly Node[]
16
+ readonly roots: readonly Node[]
17
+ readonly first: Node | null
18
+ gathered(alias: string): { readonly nodes: readonly Node[]; readonly first: Node | null }
19
+ }
20
+
21
+ /** A structured read — builder callback or AST value; `null` is idle (§1.3). */
22
+ export function useQuery(
23
+ build: ((q: QuerySource) => GatherBuilder) | QueryASTInput | null | undefined,
24
+ opts?: ReadOptions,
25
+ ): QueryResult {
26
+ const r = useRead(build ?? null, opts)
27
+ return useMemo(
28
+ () => ({
29
+ pending: r.pending,
30
+ error: r.error,
31
+ live: r.live,
32
+ seq: r.seq,
33
+ refetch: r.refetch,
34
+ nodes: r.nodes,
35
+ roots: r.roots,
36
+ first: r.first,
37
+ gathered: r.gathered,
38
+ more: r.more,
39
+ }),
40
+ [r],
41
+ )
42
+ }
package/src/index.ts ADDED
@@ -0,0 +1,202 @@
1
+ /**
2
+ * @astrale-os/shell-react — the React face of the shell (SHELL_REACT_API.md §0).
3
+ * One package, one import surface (SPEC §4.2); the bounded-context directories are
4
+ * the module structure, not subpaths. This public barrel composes the context
5
+ * barrels. Settled naming (§0): useKernelClient = the raw handle (session);
6
+ * useKernel() = the TYPED kernel (schema, no arg); useDomain(schema) = a typed
7
+ * domain binding. `useNode`/`useChildren` are single overloaded symbols (untyped
8
+ * floor + typed overloads) exported from the graph plane.
9
+ */
10
+
11
+ // ─── session (§2 provider + §3) ──────────────────────────────────────────────
12
+ export { ShellProvider } from './session'
13
+ export type { ShellError, ShellProviderProps } from './session'
14
+ export { useConnection, useKernelClient, useKernelInstance, useShell } from './session'
15
+ export type { KernelInstance } from './session'
16
+
17
+ // ─── auth (§4) ───────────────────────────────────────────────────────────────
18
+ export { ActAs, useAuth, useCan, useSelf } from './auth'
19
+ export type { ActAsProps } from './auth'
20
+
21
+ // ─── graph — the untyped floor + writes + dispatch (§5, §7) ──────────────────
22
+ // `useNode`/`useChildren` are single symbols merging the untyped floor + typed
23
+ // overloads.
24
+ export {
25
+ useCall,
26
+ useChildren,
27
+ useGraph,
28
+ useMove,
29
+ useMutate,
30
+ useNode,
31
+ useQuery,
32
+ useStream,
33
+ } from './graph'
34
+ export type {
35
+ ChildrenOptions,
36
+ ChildrenResult,
37
+ GraphMemory,
38
+ MoveAction,
39
+ Node,
40
+ NodeResult,
41
+ QueryResult,
42
+ ShapeInput,
43
+ UseMutateOptions,
44
+ } from './graph'
45
+ export type { CallOptions, StreamHandle, StreamOptions } from './graph'
46
+
47
+ // ─── schema — the typed substrate (§6) ───────────────────────────────────────
48
+ export { useDomain, useFunction, useIn, useKernel, useMethod, useOut } from './schema'
49
+ export type { FunctionOptions } from './schema'
50
+ export type { MethodOptions } from './schema'
51
+ export type {
52
+ AutoMethodAction,
53
+ BoundMethodAction,
54
+ EndpointResult,
55
+ FunctionNames,
56
+ FunctionParams,
57
+ FunctionResult,
58
+ MethodIsStatic,
59
+ MethodNames,
60
+ MethodParams,
61
+ MethodResult,
62
+ TargetedMethodAction,
63
+ TypedChildrenResult,
64
+ TypedNodeResult,
65
+ } from './schema'
66
+
67
+ // ─── navigation (§8) ─────────────────────────────────────────────────────────
68
+ export {
69
+ Link,
70
+ NavScope,
71
+ memoryAdapter,
72
+ urlAdapter,
73
+ useAncestors,
74
+ useLocation,
75
+ useNavScope,
76
+ useNavigate,
77
+ } from './navigation'
78
+ export type {
79
+ LinkProps,
80
+ MissingPolicy,
81
+ NavAdapter,
82
+ NavLocation,
83
+ NavScopeHandle,
84
+ NavScopeProps,
85
+ NavSnapshot,
86
+ NavTarget,
87
+ Navigate,
88
+ SerializedLocation,
89
+ } from './navigation'
90
+
91
+ // ─── selection (§9) ──────────────────────────────────────────────────────────
92
+ export { SelectionScope, useSelection } from './selection'
93
+ export type { Selection, SelectionScopeProps } from './selection'
94
+
95
+ // ─── window (§10) ────────────────────────────────────────────────────────────
96
+ export {
97
+ useCloseGuard,
98
+ useTargetNode,
99
+ useViewsFor,
100
+ useWindow,
101
+ useWindowList,
102
+ useWindows,
103
+ ViewHost,
104
+ ViewStage,
105
+ } from './window'
106
+ export type {
107
+ CloseGuard,
108
+ CloseVerdict,
109
+ ViewDescriptor,
110
+ ViewHostProps,
111
+ ViewSource,
112
+ ViewHostHandle,
113
+ ViewsForResult,
114
+ WindowListEntry,
115
+ WindowState,
116
+ WindowView,
117
+ } from './window'
118
+
119
+ // ─── intents (§11) ───────────────────────────────────────────────────────────
120
+ export { useIntent, useSendIntent } from './intents'
121
+ export type { IntentCtx, IntentPayload, IntentReply, SendIntent } from './intents'
122
+
123
+ // ─── application — the OS plane (§12) ────────────────────────────────────────
124
+ export {
125
+ AppHost,
126
+ AppLogo,
127
+ useApps,
128
+ useCurrentSpace,
129
+ useOpenApp,
130
+ useRoles,
131
+ useSpace,
132
+ useSpaces,
133
+ useUser,
134
+ } from './application'
135
+ export type {
136
+ AppHostFallbackState,
137
+ AppHostProps,
138
+ AppLogoProps,
139
+ AppsResult,
140
+ CurrentSpaceResult,
141
+ RolesResult,
142
+ SpaceResult,
143
+ SpacesResult,
144
+ } from './application'
145
+ // OS node/session types (from the shell package, single import surface).
146
+ export type { ApplicationNode, AppSession, RoleNode, SpaceNode, UserNode } from '@astrale-os/shell'
147
+
148
+ // ─── shared envelopes (§1) ───────────────────────────────────────────────────
149
+ export type { Action, ReadState, ReadOptions, Windowed } from './graph'
150
+
151
+ // ─── error taxonomy (§1.4) — guards + classes, re-exported from kernel-client ─
152
+ export * from './graph/guards.error'
153
+
154
+ // ─── re-exported kernel-client types (one import surface, §0 preamble) ───────
155
+ export {
156
+ DESCRIPTION_KEY,
157
+ NAME_KEY,
158
+ STATUS_KEY,
159
+ descriptionOf,
160
+ nameOf,
161
+ normalizeRef,
162
+ statusOf,
163
+ } from '@astrale-os/kernel-client'
164
+ export type {
165
+ AnyBoundNode,
166
+ BoundNode,
167
+ CreatableClassNames,
168
+ CreateProps,
169
+ CredentialInput,
170
+ DefOf,
171
+ EdgeNames,
172
+ EndpointsOf,
173
+ FnMap,
174
+ NamedNodeLike,
175
+ NodeRefInput,
176
+ SchemaBoundView,
177
+ TypeNames,
178
+ } from '@astrale-os/kernel-client'
179
+ export type {
180
+ EdgeIdentity,
181
+ GatherBuilder,
182
+ OrderInput,
183
+ PatchBuilder,
184
+ PatchInput,
185
+ PathLike,
186
+ Properties,
187
+ QueryASTInput,
188
+ QuerySource,
189
+ } from '@astrale-os/kernel-client/graph'
190
+ export type { AuthApi, Perms } from '@astrale-os/kernel-client/auth'
191
+ export type { Schema } from '@astrale-os/kernel-dsl'
192
+
193
+ // ─── re-exported shell types (one import surface, SPEC §4.2) ──────────────────
194
+ export type {
195
+ CloseResult,
196
+ KernelClient,
197
+ MountCredential,
198
+ Shell,
199
+ ShellConfig,
200
+ Window,
201
+ } from '@astrale-os/shell'
202
+ export type { IntentRegistry } from '@astrale-os/shell'
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Intent envelope construction (API §11). The typed payload/reply projections of
3
+ * a registry entry, the wire constants, and `intentMessage` — the single builder
4
+ * both directions (send + reply) share.
5
+ */
6
+ import type { IntentMessage, IntentRegistry } from '@astrale-os/shell'
7
+
8
+ /**
9
+ * Payload of a registry entry: the `{ payload }` field for the wrapped shape,
10
+ * else the whole entry (a legacy bare-payload augmentation). Robust to both, so
11
+ * bare augmentations keep compiling unchanged.
12
+ */
13
+ export type IntentPayload<K extends keyof IntentRegistry> = IntentRegistry[K] extends {
14
+ payload: infer P
15
+ }
16
+ ? P
17
+ : IntentRegistry[K]
18
+
19
+ /**
20
+ * Reply of a registry entry: the `{ reply }` field when declared, else `void`
21
+ * (bare-payload and today's reply-less entries — forward-compatible with G4).
22
+ */
23
+ export type IntentReply<K extends keyof IntentRegistry> = IntentRegistry[K] extends {
24
+ reply: infer R
25
+ }
26
+ ? R
27
+ : void
28
+
29
+ /** Sentinel `ctx.sender` carries for a parent-delivered intent (the shell stamps it). */
30
+ export const PARENT_SENDER = '<parent>'
31
+ /** Outbound `sender.windowId`; the receiver re-stamps it with the trusted id. */
32
+ const SELF_SENDER = 'self'
33
+ /** Correlated-reply channel name (the shell's fire-and-forget reply protocol). */
34
+ export const INTENT_REPLY = 'intentReply'
35
+ export const NOOP = (): void => {}
36
+
37
+ /**
38
+ * Build an intent wire message. Bare-vs-wrapped payload variance (§11 pin) is
39
+ * erased on the wire — the receiver reads `envelope.payload` verbatim; the cast
40
+ * bridges the correlated name↔payload the union type cannot express.
41
+ */
42
+ export function intentMessage(
43
+ name: string,
44
+ payload: unknown,
45
+ correlationId?: string,
46
+ ): IntentMessage {
47
+ return {
48
+ type: 'intent',
49
+ version: 1,
50
+ envelope: { name, payload, sender: { windowId: SELF_SENDER }, correlationId },
51
+ } as IntentMessage
52
+ }
@@ -0,0 +1,6 @@
1
+ // intents — API §11 (useIntent, useSendIntent). See SHELL_REACT_API.md.
2
+ export { useIntent } from './listen-intent.hook'
3
+ export type { IntentCtx } from './listen-intent.hook'
4
+ export { useSendIntent } from './send-intent.hook'
5
+ export type { SendIntent } from './send-intent.hook'
6
+ export type { IntentPayload, IntentReply } from './envelope'
@@ -0,0 +1,92 @@
1
+ import type { IntentMessage, IntentRegistry } from '@astrale-os/shell'
2
+
3
+ /**
4
+ * useIntent (API §11) — subscribe ONE handler to inbound intents (post-middleware,
5
+ * via shell.onIntent) with the REAL sender windowId and an exactly-once,
6
+ * unmount-safe `ctx.reply`. The subscription identity is independent of the
7
+ * handler closure (latest-handler ref), so re-renders never re-register. Reply
8
+ * obligations are tracked per sender window so a child close drops ours before a
9
+ * late async handler can aim a reply at a gone counterparty.
10
+ */
11
+ import { useEffect, useRef } from 'react'
12
+
13
+ import type { IntentPayload, IntentReply } from './envelope'
14
+
15
+ import { useShell } from '../session/shell.hook'
16
+ import { NOOP } from './envelope'
17
+ import { sendReply } from './reply'
18
+
19
+ /** Delivery context passed alongside an inbound intent's payload. */
20
+ export interface IntentCtx<K extends keyof IntentRegistry> {
21
+ /** REAL source windowId; the `'<parent>'` sentinel for a parent-delivered intent. */
22
+ readonly sender: string
23
+ /** Reply to the sender's pending request — exactly-once; dropped if it died. */
24
+ reply(result: IntentReply<K>): void
25
+ }
26
+
27
+ /**
28
+ * Subscribe `handler` to inbound `name` intents. `ctx.sender` is the REAL
29
+ * windowId (the channel-authenticated id, or `'<parent>'` upstream); `ctx.reply`
30
+ * answers a correlated request once.
31
+ */
32
+ export function useIntent<K extends keyof IntentRegistry>(
33
+ name: K,
34
+ handler: (payload: IntentPayload<K>, ctx: IntentCtx<K>) => void | Promise<void>,
35
+ ): void {
36
+ const shell = useShell()
37
+ const handlerRef = useRef(handler)
38
+ handlerRef.current = handler
39
+
40
+ useEffect(() => {
41
+ let alive = true
42
+ // Open reply obligations by sender window, so a child close drops ours
43
+ // before a late async handler can aim a reply at a gone counterparty.
44
+ const bySender = new Map<string, Set<() => void>>()
45
+ const track = (sender: string, drop: () => void): (() => void) => {
46
+ let set = bySender.get(sender)
47
+ if (set === undefined) {
48
+ set = new Set()
49
+ bySender.set(sender, set)
50
+ }
51
+ set.add(drop)
52
+ return () => set.delete(drop)
53
+ }
54
+ const offClose = shell.children.onClose((windowId) => {
55
+ const set = bySender.get(windowId)
56
+ if (set === undefined) return
57
+ for (const drop of set) drop()
58
+ bySender.delete(windowId)
59
+ })
60
+
61
+ const off = shell.onIntent(name, (message: IntentMessage<K>) => {
62
+ const { payload, correlationId } = message.envelope
63
+ const sender = message.envelope.sender.windowId
64
+ let settled = false
65
+ const untrack =
66
+ correlationId === undefined
67
+ ? NOOP
68
+ : track(sender, () => {
69
+ settled = true
70
+ })
71
+ const ctx: IntentCtx<K> = {
72
+ sender,
73
+ reply(result) {
74
+ if (settled || !alive) return
75
+ settled = true
76
+ untrack()
77
+ if (correlationId === undefined) return
78
+ sendReply(shell, sender, correlationId, result)
79
+ },
80
+ }
81
+ void Promise.resolve(handlerRef.current(payload as IntentPayload<K>, ctx))
82
+ .catch(NOOP)
83
+ .finally(untrack)
84
+ })
85
+
86
+ return () => {
87
+ alive = false
88
+ off()
89
+ offClose()
90
+ }
91
+ }, [shell, name])
92
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Reply delivery (API §11) — route a correlated reply to whichever channel the
3
+ * request arrived on (parent vs a child window). Best-effort: a gone counterparty
4
+ * drops the reply safely (counterparty-death cleanup).
5
+ */
6
+ import type { Shell } from '@astrale-os/shell'
7
+
8
+ import { INTENT_REPLY, PARENT_SENDER, intentMessage } from './envelope'
9
+
10
+ /** Deliver a reply to whichever channel the request arrived on. Best-effort. */
11
+ export function sendReply(
12
+ shell: Shell,
13
+ sender: string,
14
+ correlationId: string,
15
+ result: unknown,
16
+ ): void {
17
+ const message = intentMessage(INTENT_REPLY, { correlationId, result })
18
+ try {
19
+ if (sender === PARENT_SENDER) shell.parent?.send(message)
20
+ else shell.children.send(sender, message) // no-op if the child has detached
21
+ } catch {
22
+ // Counterparty gone — the reply is safely dropped.
23
+ }
24
+ }