@astrale-os/shell-react 0.2.8 → 0.2.10-beta.2

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 (342) hide show
  1. package/dist/application/apps/app-host.d.ts +7 -15
  2. package/dist/application/apps/app-host.d.ts.map +1 -1
  3. package/dist/application/apps/app-host.js +3 -2
  4. package/dist/application/apps/apps.hook.d.ts +7 -4
  5. package/dist/application/apps/apps.hook.d.ts.map +1 -1
  6. package/dist/application/apps/apps.hook.js +10 -2
  7. package/dist/application/apps/domains.hook.d.ts +1 -1
  8. package/dist/application/apps/domains.hook.d.ts.map +1 -1
  9. package/dist/application/apps/domains.hook.js +1 -1
  10. package/dist/application/apps/open-app.hook.d.ts +2 -3
  11. package/dist/application/apps/open-app.hook.d.ts.map +1 -1
  12. package/dist/application/index.d.ts +2 -4
  13. package/dist/application/index.d.ts.map +1 -1
  14. package/dist/application/index.js +2 -3
  15. package/dist/application/spaces/space.hook.d.ts +2 -2
  16. package/dist/application/spaces/space.hook.d.ts.map +1 -1
  17. package/dist/application/spaces/space.hook.js +2 -2
  18. package/dist/application/spaces/spaces.hook.d.ts +2 -3
  19. package/dist/application/spaces/spaces.hook.d.ts.map +1 -1
  20. package/dist/application/spaces/spaces.hook.js +10 -3
  21. package/dist/auth/act-as.context.d.ts +15 -32
  22. package/dist/auth/act-as.context.d.ts.map +1 -1
  23. package/dist/auth/act-as.context.js +22 -66
  24. package/dist/auth/auth.hook.d.ts +1 -0
  25. package/dist/auth/auth.hook.d.ts.map +1 -1
  26. package/dist/auth/auth.hook.js +2 -22
  27. package/dist/auth/can.hook.d.ts +10 -38
  28. package/dist/auth/can.hook.d.ts.map +1 -1
  29. package/dist/auth/can.hook.js +35 -110
  30. package/dist/auth/index.d.ts +1 -0
  31. package/dist/auth/index.d.ts.map +1 -1
  32. package/dist/auth/self.hook.d.ts +2 -2
  33. package/dist/auth/self.hook.d.ts.map +1 -1
  34. package/dist/auth/self.hook.js +0 -7
  35. package/dist/auth/session-kernel.d.ts +2 -3
  36. package/dist/auth/session-kernel.d.ts.map +1 -1
  37. package/dist/auth/session-kernel.js +4 -13
  38. package/dist/components/boundaries.d.ts +47 -0
  39. package/dist/components/boundaries.d.ts.map +1 -0
  40. package/dist/components/boundaries.js +55 -0
  41. package/dist/components/can.d.ts +11 -0
  42. package/dist/components/can.d.ts.map +1 -0
  43. package/dist/components/can.js +5 -0
  44. package/dist/components/index.d.ts +7 -0
  45. package/dist/components/index.d.ts.map +1 -0
  46. package/dist/components/index.js +4 -0
  47. package/dist/components/mount-domain-frontend.d.ts +22 -0
  48. package/dist/components/mount-domain-frontend.d.ts.map +1 -0
  49. package/dist/components/mount-domain-frontend.js +85 -0
  50. package/dist/components/view-app.d.ts +16 -0
  51. package/dist/components/view-app.d.ts.map +1 -0
  52. package/dist/components/view-app.js +16 -0
  53. package/dist/graph/call/call.hook.d.ts +8 -18
  54. package/dist/graph/call/call.hook.d.ts.map +1 -1
  55. package/dist/graph/call/call.hook.js +7 -13
  56. package/dist/graph/call/stream.hook.d.ts +12 -20
  57. package/dist/graph/call/stream.hook.d.ts.map +1 -1
  58. package/dist/graph/call/stream.hook.js +55 -111
  59. package/dist/graph/graph.context.d.ts +4 -3
  60. package/dist/graph/graph.context.d.ts.map +1 -1
  61. package/dist/graph/graph.context.js +2 -8
  62. package/dist/graph/guards.error.d.ts +7 -8
  63. package/dist/graph/guards.error.d.ts.map +1 -1
  64. package/dist/graph/guards.error.js +11 -7
  65. package/dist/graph/index.d.ts +8 -16
  66. package/dist/graph/index.d.ts.map +1 -1
  67. package/dist/graph/index.js +3 -7
  68. package/dist/graph/memory/read/envelope.d.ts +22 -54
  69. package/dist/graph/memory/read/envelope.d.ts.map +1 -1
  70. package/dist/graph/memory/read/envelope.js +26 -20
  71. package/dist/graph/memory/read/idle.d.ts +1 -15
  72. package/dist/graph/memory/read/idle.d.ts.map +1 -1
  73. package/dist/graph/memory/read/idle.js +4 -13
  74. package/dist/graph/query-definition.hook.d.ts +11 -0
  75. package/dist/graph/query-definition.hook.d.ts.map +1 -0
  76. package/dist/graph/query-definition.hook.js +205 -0
  77. package/dist/graph/resource.d.ts +34 -0
  78. package/dist/graph/resource.d.ts.map +1 -0
  79. package/dist/graph/resource.js +244 -0
  80. package/dist/graph/store.d.ts +25 -0
  81. package/dist/graph/store.d.ts.map +1 -0
  82. package/dist/graph/store.js +69 -0
  83. package/dist/host/actions.hook.d.ts +34 -0
  84. package/dist/host/actions.hook.d.ts.map +1 -0
  85. package/dist/host/actions.hook.js +125 -0
  86. package/dist/host/components.d.ts +15 -0
  87. package/dist/host/components.d.ts.map +1 -0
  88. package/dist/host/components.js +14 -0
  89. package/dist/host/index.d.ts +5 -0
  90. package/dist/host/index.d.ts.map +1 -0
  91. package/dist/host/index.js +3 -0
  92. package/dist/index.d.ts +26 -37
  93. package/dist/index.d.ts.map +1 -1
  94. package/dist/index.js +20 -31
  95. package/dist/intents/envelope.d.ts +7 -19
  96. package/dist/intents/envelope.d.ts.map +1 -1
  97. package/dist/intents/envelope.js +2 -3
  98. package/dist/intents/send-intent.hook.d.ts +5 -7
  99. package/dist/intents/send-intent.hook.d.ts.map +1 -1
  100. package/dist/intents/send-intent.hook.js +17 -75
  101. package/dist/navigation/adapters/adapter.d.ts +5 -0
  102. package/dist/navigation/adapters/adapter.d.ts.map +1 -1
  103. package/dist/navigation/adapters/memory.adapter.d.ts.map +1 -1
  104. package/dist/navigation/adapters/memory.adapter.js +4 -0
  105. package/dist/navigation/adapters/url.adapter.d.ts.map +1 -1
  106. package/dist/navigation/adapters/url.adapter.js +57 -10
  107. package/dist/navigation/codec.d.ts +60 -0
  108. package/dist/navigation/codec.d.ts.map +1 -0
  109. package/dist/navigation/codec.js +121 -0
  110. package/dist/navigation/history.d.ts +11 -9
  111. package/dist/navigation/history.d.ts.map +1 -1
  112. package/dist/navigation/history.js +6 -8
  113. package/dist/navigation/hooks/ancestors.hook.d.ts +5 -7
  114. package/dist/navigation/hooks/ancestors.hook.d.ts.map +1 -1
  115. package/dist/navigation/hooks/ancestors.hook.js +7 -51
  116. package/dist/navigation/hooks/location.hook.d.ts.map +1 -1
  117. package/dist/navigation/hooks/location.hook.js +4 -6
  118. package/dist/navigation/hooks/scope.hook.d.ts.map +1 -1
  119. package/dist/navigation/hooks/scope.hook.js +4 -6
  120. package/dist/navigation/index.d.ts +4 -2
  121. package/dist/navigation/index.d.ts.map +1 -1
  122. package/dist/navigation/index.js +2 -1
  123. package/dist/navigation/link.d.ts +12 -0
  124. package/dist/navigation/link.d.ts.map +1 -1
  125. package/dist/navigation/link.js +13 -2
  126. package/dist/navigation/location.d.ts +13 -40
  127. package/dist/navigation/location.d.ts.map +1 -1
  128. package/dist/navigation/location.js +14 -76
  129. package/dist/navigation/scope.context.d.ts +17 -26
  130. package/dist/navigation/scope.context.d.ts.map +1 -1
  131. package/dist/navigation/scope.context.js +140 -108
  132. package/dist/navigation/snapshot.d.ts +14 -19
  133. package/dist/navigation/snapshot.d.ts.map +1 -1
  134. package/dist/navigation/snapshot.js +48 -44
  135. package/dist/schema/action.hook.d.ts +44 -0
  136. package/dist/schema/action.hook.d.ts.map +1 -0
  137. package/dist/schema/action.hook.js +72 -0
  138. package/dist/schema/domain.hook.d.ts +12 -7
  139. package/dist/schema/domain.hook.d.ts.map +1 -1
  140. package/dist/schema/domain.hook.js +101 -4
  141. package/dist/schema/index.d.ts +9 -8
  142. package/dist/schema/index.d.ts.map +1 -1
  143. package/dist/schema/index.js +5 -7
  144. package/dist/schema/kernel.hook.d.ts +4 -9
  145. package/dist/schema/kernel.hook.d.ts.map +1 -1
  146. package/dist/schema/kernel.hook.js +4 -6
  147. package/dist/schema/mutation.hook.d.ts +9 -0
  148. package/dist/schema/mutation.hook.d.ts.map +1 -0
  149. package/dist/schema/mutation.hook.js +41 -0
  150. package/dist/schema/object.hook.d.ts +16 -0
  151. package/dist/schema/object.hook.d.ts.map +1 -0
  152. package/dist/schema/object.hook.js +46 -0
  153. package/dist/schema/relation.hook.d.ts +46 -0
  154. package/dist/schema/relation.hook.d.ts.map +1 -0
  155. package/dist/schema/relation.hook.js +136 -0
  156. package/dist/selection/live.d.ts +9 -17
  157. package/dist/selection/live.d.ts.map +1 -1
  158. package/dist/selection/live.js +65 -112
  159. package/dist/selection/scope.context.js +5 -4
  160. package/dist/session/boot/boot.d.ts +1 -13
  161. package/dist/session/boot/boot.d.ts.map +1 -1
  162. package/dist/session/boot/boot.js +9 -27
  163. package/dist/session/index.d.ts +1 -0
  164. package/dist/session/index.d.ts.map +1 -1
  165. package/dist/session/kernel-instance.hook.d.ts +1 -4
  166. package/dist/session/kernel-instance.hook.d.ts.map +1 -1
  167. package/dist/session/kernel-instance.hook.js +1 -5
  168. package/dist/session/provider.d.ts +9 -23
  169. package/dist/session/provider.d.ts.map +1 -1
  170. package/dist/session/provider.js +29 -75
  171. package/dist/session/session.context.d.ts +5 -0
  172. package/dist/session/session.context.d.ts.map +1 -1
  173. package/dist/window/adopt.d.ts +0 -1
  174. package/dist/window/adopt.d.ts.map +1 -1
  175. package/dist/window/hooks/target-node.hook.d.ts +10 -6
  176. package/dist/window/hooks/target-node.hook.d.ts.map +1 -1
  177. package/dist/window/hooks/target-node.hook.js +14 -72
  178. package/dist/window/hooks/views-for.hook.d.ts +4 -3
  179. package/dist/window/hooks/views-for.hook.d.ts.map +1 -1
  180. package/dist/window/hooks/views-for.hook.js +6 -22
  181. package/dist/window/hooks/windows.hook.d.ts.map +1 -1
  182. package/dist/window/hooks/windows.hook.js +3 -4
  183. package/dist/window/host.d.ts +11 -11
  184. package/dist/window/host.d.ts.map +1 -1
  185. package/dist/window/host.js +38 -54
  186. package/dist/window/hub.d.ts +2 -0
  187. package/dist/window/hub.d.ts.map +1 -1
  188. package/dist/window/hub.js +33 -0
  189. package/dist/window/index.d.ts +3 -3
  190. package/dist/window/index.d.ts.map +1 -1
  191. package/dist/window/index.js +2 -2
  192. package/dist/window/lifecycle/mount.d.ts +5 -44
  193. package/dist/window/lifecycle/mount.d.ts.map +1 -1
  194. package/dist/window/lifecycle/mount.js +14 -120
  195. package/dist/window/lifecycle/states.d.ts +0 -5
  196. package/dist/window/lifecycle/states.d.ts.map +1 -1
  197. package/dist/window/stage.d.ts.map +1 -1
  198. package/dist/window/stage.js +1 -0
  199. package/package.json +9 -8
  200. package/src/application/apps/app-host.tsx +17 -18
  201. package/src/application/apps/apps.hook.ts +13 -6
  202. package/src/application/apps/domains.hook.ts +1 -1
  203. package/src/application/apps/open-app.hook.ts +3 -4
  204. package/src/application/index.ts +4 -6
  205. package/src/application/spaces/space.hook.ts +5 -6
  206. package/src/application/spaces/spaces.hook.ts +14 -9
  207. package/src/auth/act-as.context.tsx +37 -91
  208. package/src/auth/auth.hook.ts +2 -30
  209. package/src/auth/can.hook.ts +44 -135
  210. package/src/auth/index.ts +1 -0
  211. package/src/auth/self.hook.ts +2 -9
  212. package/src/auth/session-kernel.ts +6 -16
  213. package/src/components/boundaries.tsx +113 -0
  214. package/src/components/can.tsx +16 -0
  215. package/src/components/index.ts +14 -0
  216. package/src/components/mount-domain-frontend.tsx +158 -0
  217. package/src/components/view-app.tsx +49 -0
  218. package/src/graph/call/call.hook.ts +22 -27
  219. package/src/graph/call/stream.hook.ts +66 -143
  220. package/src/graph/graph.context.ts +6 -11
  221. package/src/graph/guards.error.ts +17 -55
  222. package/src/graph/index.ts +23 -17
  223. package/src/graph/memory/read/envelope.ts +40 -65
  224. package/src/graph/memory/read/idle.ts +5 -18
  225. package/src/graph/query-definition.hook.ts +270 -0
  226. package/src/graph/resource.ts +358 -0
  227. package/src/graph/store.ts +97 -0
  228. package/src/host/actions.hook.ts +165 -0
  229. package/src/host/components.tsx +29 -0
  230. package/src/host/index.ts +17 -0
  231. package/src/index.ts +137 -123
  232. package/src/intents/envelope.ts +7 -23
  233. package/src/intents/send-intent.hook.ts +24 -79
  234. package/src/navigation/adapters/adapter.ts +3 -0
  235. package/src/navigation/adapters/memory.adapter.ts +4 -0
  236. package/src/navigation/adapters/url.adapter.ts +48 -8
  237. package/src/navigation/codec.ts +190 -0
  238. package/src/navigation/history.ts +20 -11
  239. package/src/navigation/hooks/ancestors.hook.ts +10 -60
  240. package/src/navigation/hooks/location.hook.ts +4 -6
  241. package/src/navigation/hooks/scope.hook.ts +4 -6
  242. package/src/navigation/index.ts +18 -2
  243. package/src/navigation/link.tsx +27 -2
  244. package/src/navigation/location.ts +26 -93
  245. package/src/navigation/scope.context.tsx +151 -136
  246. package/src/navigation/snapshot.ts +69 -50
  247. package/src/schema/action.hook.ts +146 -0
  248. package/src/schema/domain.hook.ts +127 -11
  249. package/src/schema/index.ts +9 -30
  250. package/src/schema/kernel.hook.ts +6 -13
  251. package/src/schema/mutation.hook.ts +63 -0
  252. package/src/schema/object.hook.ts +85 -0
  253. package/src/schema/relation.hook.ts +253 -0
  254. package/src/selection/live.ts +75 -125
  255. package/src/selection/scope.context.tsx +4 -4
  256. package/src/session/boot/boot.ts +8 -33
  257. package/src/session/index.ts +1 -0
  258. package/src/session/kernel-instance.hook.ts +2 -11
  259. package/src/session/provider.tsx +34 -103
  260. package/src/session/session.context.ts +6 -0
  261. package/src/window/adopt.ts +0 -1
  262. package/src/window/hooks/target-node.hook.ts +20 -85
  263. package/src/window/hooks/views-for.hook.ts +12 -25
  264. package/src/window/hooks/windows.hook.ts +3 -4
  265. package/src/window/host.tsx +51 -68
  266. package/src/window/hub.ts +33 -0
  267. package/src/window/index.ts +4 -4
  268. package/src/window/lifecycle/mount.ts +15 -160
  269. package/src/window/lifecycle/states.ts +1 -5
  270. package/src/window/stage.tsx +1 -0
  271. package/dist/application/roles.hook.d.ts +0 -7
  272. package/dist/application/roles.hook.d.ts.map +0 -1
  273. package/dist/application/roles.hook.js +0 -23
  274. package/dist/graph/memory/base.d.ts +0 -21
  275. package/dist/graph/memory/base.d.ts.map +0 -1
  276. package/dist/graph/memory/base.js +0 -33
  277. package/dist/graph/memory/read/read.d.ts +0 -9
  278. package/dist/graph/memory/read/read.d.ts.map +0 -1
  279. package/dist/graph/memory/read/read.js +0 -87
  280. package/dist/graph/memory/scope.d.ts +0 -7
  281. package/dist/graph/memory/scope.d.ts.map +0 -1
  282. package/dist/graph/memory/scope.js +0 -25
  283. package/dist/graph/mutate/move.hook.d.ts +0 -7
  284. package/dist/graph/mutate/move.hook.d.ts.map +0 -1
  285. package/dist/graph/mutate/move.hook.js +0 -49
  286. package/dist/graph/mutate/mutate.hook.d.ts +0 -17
  287. package/dist/graph/mutate/mutate.hook.d.ts.map +0 -1
  288. package/dist/graph/mutate/mutate.hook.js +0 -45
  289. package/dist/graph/query/children.hook.d.ts +0 -29
  290. package/dist/graph/query/children.hook.d.ts.map +0 -1
  291. package/dist/graph/query/children.hook.js +0 -64
  292. package/dist/graph/query/graph.hook.d.ts +0 -9
  293. package/dist/graph/query/graph.hook.d.ts.map +0 -1
  294. package/dist/graph/query/graph.hook.js +0 -5
  295. package/dist/graph/query/neighbors.hook.d.ts +0 -7
  296. package/dist/graph/query/neighbors.hook.d.ts.map +0 -1
  297. package/dist/graph/query/neighbors.hook.js +0 -104
  298. package/dist/graph/query/node.hook.d.ts +0 -14
  299. package/dist/graph/query/node.hook.d.ts.map +0 -1
  300. package/dist/graph/query/node.hook.js +0 -43
  301. package/dist/graph/query/query.hook.d.ts +0 -14
  302. package/dist/graph/query/query.hook.d.ts.map +0 -1
  303. package/dist/graph/query/query.hook.js +0 -23
  304. package/dist/schema/bind-read.d.ts +0 -37
  305. package/dist/schema/bind-read.d.ts.map +0 -1
  306. package/dist/schema/bind-read.js +0 -205
  307. package/dist/schema/binding.d.ts +0 -21
  308. package/dist/schema/binding.d.ts.map +0 -1
  309. package/dist/schema/binding.js +0 -99
  310. package/dist/schema/function.hook.d.ts +0 -9
  311. package/dist/schema/function.hook.d.ts.map +0 -1
  312. package/dist/schema/function.hook.js +0 -40
  313. package/dist/schema/method.hook.d.ts +0 -14
  314. package/dist/schema/method.hook.d.ts.map +0 -1
  315. package/dist/schema/method.hook.js +0 -49
  316. package/dist/schema/tag.d.ts +0 -11
  317. package/dist/schema/tag.d.ts.map +0 -1
  318. package/dist/schema/tag.js +0 -11
  319. package/dist/schema/types.d.ts +0 -73
  320. package/dist/schema/types.d.ts.map +0 -1
  321. package/dist/schema/types.js +0 -1
  322. package/dist/schema/write.d.ts +0 -16
  323. package/dist/schema/write.d.ts.map +0 -1
  324. package/dist/schema/write.js +0 -56
  325. package/src/application/roles.hook.ts +0 -41
  326. package/src/graph/memory/base.ts +0 -50
  327. package/src/graph/memory/read/read.ts +0 -103
  328. package/src/graph/memory/scope.ts +0 -29
  329. package/src/graph/mutate/move.hook.ts +0 -70
  330. package/src/graph/mutate/mutate.hook.ts +0 -69
  331. package/src/graph/query/children.hook.ts +0 -136
  332. package/src/graph/query/graph.hook.ts +0 -13
  333. package/src/graph/query/neighbors.hook.ts +0 -159
  334. package/src/graph/query/node.hook.ts +0 -83
  335. package/src/graph/query/query.hook.ts +0 -42
  336. package/src/schema/bind-read.ts +0 -319
  337. package/src/schema/binding.ts +0 -146
  338. package/src/schema/function.hook.ts +0 -68
  339. package/src/schema/method.hook.ts +0 -103
  340. package/src/schema/tag.ts +0 -19
  341. package/src/schema/types.ts +0 -150
  342. package/src/schema/write.ts +0 -96
@@ -3,28 +3,13 @@
3
3
  * a registry entry, the wire constants, and `intentMessage` — the single builder
4
4
  * both directions (send + reply) share.
5
5
  */
6
- import type { IntentMessage, IntentRegistry } from '@astrale-os/shell'
6
+ import type { IntentMessage, IntentRegistry, PayloadOf, ReplyOf } from '@astrale-os/shell'
7
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]
8
+ /** Payload declared by the canonical Shell intent registry entry. */
9
+ export type IntentPayload<K extends keyof IntentRegistry> = PayloadOf<K>
18
10
 
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
11
+ /** Reply declared by the canonical Shell intent registry entry. */
12
+ export type IntentReply<K extends keyof IntentRegistry> = ReplyOf<K>
28
13
 
29
14
  /** Sentinel `ctx.sender` carries for a parent-delivered intent (the shell stamps it). */
30
15
  export const PARENT_SENDER = '<parent>'
@@ -35,9 +20,8 @@ export const INTENT_REPLY = 'intentReply'
35
20
  export const NOOP = (): void => {}
36
21
 
37
22
  /**
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.
23
+ * Build an intent wire message. The cast bridges the correlated name↔payload
24
+ * relation that the generic union type cannot express.
41
25
  */
42
26
  export function intentMessage(
43
27
  name: string,
@@ -1,108 +1,53 @@
1
- import type { IntentMessage, IntentRegistry } from '@astrale-os/shell'
1
+ import type { IntentRegistry, RequestIntentOptions } from '@astrale-os/shell'
2
2
 
3
- /**
4
- * useSendIntent (API §11) — a stable typed sender. Each call attaches a
5
- * `correlationId`, subscribes for the matching `intentReply` on the origin channel
6
- * BEFORE sending, then resolves with the reply's result. The promise also resolves
7
- * (`void`) if the target window closes or this hook unmounts — a pending reply
8
- * never strands.
9
- */
3
+ import { requestChildIntent, requestIntent } from '@astrale-os/shell'
10
4
  import { useCallback, useEffect, useRef } from 'react'
11
5
 
12
6
  import type { IntentPayload, IntentReply } from './envelope.js'
13
7
 
14
8
  import { useShell } from '../session/shell.hook.js'
15
- import { INTENT_REPLY, NOOP, intentMessage } from './envelope.js'
16
9
  import { resolveIntentTarget } from './resolve-intent-target.js'
17
10
 
18
- /** The typed sender returned by {@link useSendIntent}. */
11
+ /** Typed request sender backed exclusively by Shell's core correlation implementation. */
19
12
  export interface SendIntent {
20
13
  <K extends keyof IntentRegistry>(
21
14
  name: K,
22
15
  payload: IntentPayload<K>,
23
- /** `to`: `'parent'` (reserved) or a child windowId. Default: the parent, if any. */
24
- opts?: { to?: 'parent' | string },
25
- ): Promise<IntentReply<K> | void>
16
+ options?: RequestIntentOptions & { readonly to?: 'parent' | string },
17
+ ): Promise<IntentReply<K>>
26
18
  }
27
19
 
28
20
  export function useSendIntent(): SendIntent {
29
21
  const shell = useShell()
30
- // Unmount settlers for in-flight sends, so a pending reply resolves on teardown.
31
- const pendingRef = useRef<Set<() => void>>(new Set())
22
+ const lifetime = useRef(new AbortController())
23
+
32
24
  useEffect(() => {
33
- const pending = pendingRef.current
34
- return () => {
35
- for (const settle of pending) settle()
36
- pending.clear()
37
- }
25
+ const controller = lifetime.current.signal.aborted ? new AbortController() : lifetime.current
26
+ lifetime.current = controller
27
+ return () => controller.abort(new Error('Intent requester unmounted'))
38
28
  }, [])
39
29
 
40
30
  return useCallback(
41
31
  <K extends keyof IntentRegistry>(
42
32
  name: K,
43
33
  payload: IntentPayload<K>,
44
- opts?: { to?: 'parent' | string },
45
- ): Promise<IntentReply<K> | void> => {
46
- const target = resolveIntentTarget(shell, opts?.to)
34
+ options?: RequestIntentOptions & { readonly to?: 'parent' | string },
35
+ ): Promise<IntentReply<K>> => {
36
+ const target = resolveIntentTarget(shell, options?.to)
47
37
  if (target.kind === 'invalid') {
48
38
  return Promise.reject(new Error(`useSendIntent: ${target.reason}`))
49
39
  }
50
- const correlationId = crypto.randomUUID()
51
- const message = intentMessage(name, payload, correlationId)
52
- const pending = pendingRef.current
53
-
54
- return new Promise<IntentReply<K> | void>((resolve, reject) => {
55
- let done = false
56
- let offReply = NOOP
57
- let offClose = NOOP
58
- const onUnmount = (): void => settle(undefined)
59
- function settle(value: IntentReply<K> | void): void {
60
- if (done) return
61
- done = true
62
- offReply()
63
- offClose()
64
- pending.delete(onUnmount)
65
- resolve(value)
66
- }
67
- function fail(error: Error): void {
68
- if (done) return
69
- done = true
70
- offReply()
71
- offClose()
72
- pending.delete(onUnmount)
73
- reject(error)
74
- }
75
- const onReply = (m: IntentMessage): void => {
76
- if (m.envelope.name !== INTENT_REPLY) return
77
- const reply = m.envelope.payload as {
78
- correlationId: string
79
- result?: unknown
80
- error?: { message: string }
81
- }
82
- if (reply.correlationId !== correlationId) return
83
- if (reply.error) {
84
- fail(new Error(reply.error.message))
85
- return
86
- }
87
- settle(reply.result as IntentReply<K>)
88
- }
89
-
90
- pending.add(onUnmount)
91
- if (target.kind === 'parent') {
92
- const parent = shell.parent!
93
- offReply = parent.on('intent', onReply)
94
- parent.send(message)
95
- } else {
96
- const { windowId } = target
97
- offReply = shell.children.on('intent', (wid, m) => {
98
- if (wid === windowId) onReply(m)
99
- })
100
- offClose = shell.children.onClose((wid) => {
101
- if (wid === windowId) settle(undefined)
102
- })
103
- shell.children.send(windowId, message)
104
- }
105
- })
40
+ const signal =
41
+ options?.signal === undefined
42
+ ? lifetime.current.signal
43
+ : AbortSignal.any([lifetime.current.signal, options.signal])
44
+ const requestOptions: RequestIntentOptions = {
45
+ signal,
46
+ ...(options?.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
47
+ }
48
+ return target.kind === 'parent'
49
+ ? requestIntent(shell.parent!, name, payload, requestOptions)
50
+ : requestChildIntent(shell.children, target.windowId, name, payload, requestOptions)
106
51
  },
107
52
  [shell],
108
53
  )
@@ -6,8 +6,11 @@
6
6
  import type { SerializedLocation } from '../history.js'
7
7
 
8
8
  export interface NavAdapter {
9
+ readonly kind: 'memory' | 'browser'
9
10
  read(): SerializedLocation | null
10
11
  write(loc: SerializedLocation, opts: { replace: boolean }): void
12
+ /** Remove only this adapter's owned projection when no history entry survives. */
13
+ clear(opts: { replace: boolean }): void
11
14
  subscribe(onExternalChange: () => void): () => void
12
15
  href?(loc: SerializedLocation): string
13
16
  }
@@ -10,10 +10,14 @@ export function memoryAdapter(initial?: SerializedLocation): NavAdapter {
10
10
  let current: SerializedLocation | null = initial ?? null
11
11
  const listeners = new Set<() => void>()
12
12
  return {
13
+ kind: 'memory',
13
14
  read: () => current,
14
15
  write: (loc) => {
15
16
  current = loc
16
17
  },
18
+ clear: () => {
19
+ current = null
20
+ },
17
21
  subscribe: (cb) => {
18
22
  listeners.add(cb)
19
23
  return () => listeners.delete(cb)
@@ -1,3 +1,6 @@
1
+ import { NodePath } from '@astrale-os/sdk/graph/path'
2
+ import { Key } from '@astrale-os/sdk/schema'
3
+
1
4
  /**
2
5
  * urlAdapter (API §8.1) — the History-API adapter: mirrors the location into the
3
6
  * query string (URL form → `<a href>`), owning the nav query keys (`param`,
@@ -6,35 +9,62 @@
6
9
  import type { SerializedLocation } from '../history.js'
7
10
  import type { NavAdapter } from './adapter.js'
8
11
 
9
- const VIEW_KEY = 'view'
12
+ const VIEW_KEY = 'astrale.view'
13
+ const PARAM_PREFIX = 'astrale.param.'
10
14
 
11
15
  /** History-API adapter — the location lives in the query string. */
12
16
  export function urlAdapter(opts?: { param?: string }): NavAdapter {
13
- const param = opts?.param ?? 'node'
17
+ const param = opts?.param ?? 'astrale.node'
14
18
  const hasWindow = (): boolean => typeof window !== 'undefined'
15
19
 
16
20
  const build = (loc: SerializedLocation): string => {
17
21
  const url = new URL(window.location.href)
18
- const sp = new URLSearchParams()
22
+ const sp = url.searchParams
19
23
  sp.set(param, loc.node)
20
24
  if (loc.view !== undefined) sp.set(VIEW_KEY, loc.view)
25
+ else sp.delete(VIEW_KEY)
26
+ for (const key of sp.keys()) if (key.startsWith(PARAM_PREFIX)) sp.delete(key)
21
27
  for (const [k, v] of Object.entries(loc.params ?? {})) {
22
- if (k !== param && k !== VIEW_KEY) sp.set(k, v)
28
+ if (isSensitive(k)) throw new TypeError(`Navigation param '${k}' is sensitive.`)
29
+ sp.set(`${PARAM_PREFIX}${k}`, v)
23
30
  }
24
31
  url.search = sp.toString()
25
32
  return `${url.pathname}${url.search}${url.hash}`
26
33
  }
27
34
 
35
+ const clear = (): string => {
36
+ const url = new URL(window.location.href)
37
+ const sp = url.searchParams
38
+ sp.delete(param)
39
+ sp.delete(VIEW_KEY)
40
+ for (const key of sp.keys()) if (key.startsWith(PARAM_PREFIX)) sp.delete(key)
41
+ url.search = sp.toString()
42
+ return `${url.pathname}${url.search}${url.hash}`
43
+ }
44
+
28
45
  return {
46
+ kind: 'browser',
29
47
  read() {
30
48
  if (!hasWindow()) return null
31
49
  const sp = new URL(window.location.href).searchParams
32
- const node = sp.get(param)
33
- if (node === null) return null
34
- const view = sp.get(VIEW_KEY) ?? undefined
50
+ const rawNode = sp.get(param)
51
+ if (rawNode === null) return null
52
+ let node
53
+ let view
54
+ try {
55
+ node = NodePath(rawNode)
56
+ const rawView = sp.get(VIEW_KEY)
57
+ if (rawView !== null) {
58
+ const admitted = Key(rawView)
59
+ if (!Key.ref(admitted).startsWith('view.')) throw new TypeError('Invalid View key.')
60
+ view = admitted as Key.View
61
+ }
62
+ } catch {
63
+ return null
64
+ }
35
65
  const params: Record<string, string> = {}
36
66
  for (const [k, v] of sp) {
37
- if (k !== param && k !== VIEW_KEY) params[k] = v
67
+ if (k.startsWith(PARAM_PREFIX)) params[k.slice(PARAM_PREFIX.length)] = v
38
68
  }
39
69
  return {
40
70
  node,
@@ -48,6 +78,12 @@ export function urlAdapter(opts?: { param?: string }): NavAdapter {
48
78
  if (replace) window.history.replaceState(null, '', href)
49
79
  else window.history.pushState(null, '', href)
50
80
  },
81
+ clear({ replace }) {
82
+ if (!hasWindow()) return
83
+ const href = clear()
84
+ if (replace) window.history.replaceState(null, '', href)
85
+ else window.history.pushState(null, '', href)
86
+ },
51
87
  subscribe(cb) {
52
88
  if (!hasWindow()) return () => undefined
53
89
  const handler = (): void => cb()
@@ -59,3 +95,7 @@ export function urlAdapter(opts?: { param?: string }): NavAdapter {
59
95
  },
60
96
  }
61
97
  }
98
+
99
+ function isSensitive(key: string): boolean {
100
+ return /token|credential|authorization|secret/i.test(key)
101
+ }
@@ -0,0 +1,190 @@
1
+ import type { Key } from '@astrale-os/sdk/schema'
2
+ import type { ReactElement, ReactNode } from 'react'
3
+
4
+ import { Path } from '@astrale-os/sdk/graph/path'
5
+ import { Key as admitKey } from '@astrale-os/sdk/schema'
6
+ import { createElement } from 'react'
7
+
8
+ import type { NavAdapter } from './adapters/adapter.js'
9
+ import type { NavSnapshot, SerializedLocation } from './history.js'
10
+
11
+ import { NavScope } from './scope.context.js'
12
+ import { useNavCtx } from './scope.context.js'
13
+ import { useHistoryVersion } from './scope.context.js'
14
+
15
+ export interface NavigationParam<Value> {
16
+ encode(value: Value): string
17
+ decode(value: string | undefined): Value
18
+ }
19
+
20
+ export function pathParam(_name: string): NavigationParam<Path> {
21
+ return Object.freeze({
22
+ encode: (value: Path) => Path.from(value).raw,
23
+ decode(value: string | undefined) {
24
+ if (value === undefined) throw new TypeError('Navigation target is missing.')
25
+ return Path.parse(value)
26
+ },
27
+ })
28
+ }
29
+
30
+ export function enumParam<const Values extends readonly [string, ...string[]]>(
31
+ values: Values,
32
+ fallback: Values[number],
33
+ ): NavigationParam<Values[number]> {
34
+ if (!values.includes(fallback)) throw new TypeError('Navigation enum fallback is not admitted.')
35
+ return Object.freeze({
36
+ encode(value: Values[number]) {
37
+ if (!values.includes(value)) throw new TypeError(`Invalid navigation enum value '${value}'.`)
38
+ return value
39
+ },
40
+ decode(value: string | undefined) {
41
+ if (value === undefined) return fallback
42
+ if (!values.includes(value)) throw new TypeError(`Invalid navigation enum value '${value}'.`)
43
+ return value as Values[number]
44
+ },
45
+ })
46
+ }
47
+
48
+ export type NavigationParams = Readonly<Record<string, NavigationParam<unknown>>>
49
+
50
+ export interface LocationDefinition<Target, Params extends NavigationParams> {
51
+ readonly view: Key.View
52
+ readonly target: NavigationParam<Target>
53
+ readonly params: Params
54
+ }
55
+
56
+ export function location<
57
+ Target,
58
+ const Params extends NavigationParams = Readonly<Record<never, never>>,
59
+ >(input: {
60
+ readonly view: Key.View | { readonly key: Key.View }
61
+ readonly target: NavigationParam<Target>
62
+ readonly params?: Params
63
+ }): LocationDefinition<Target, Params> {
64
+ return Object.freeze({
65
+ view: viewKey(typeof input.view === 'string' ? input.view : input.view.key),
66
+ target: input.target,
67
+ params: Object.freeze({ ...input.params }) as Params,
68
+ })
69
+ }
70
+
71
+ type ValueOf<Codec> = Codec extends NavigationParam<infer Value> ? Value : never
72
+ type RouteTarget<
73
+ Routes extends Readonly<Record<string, LocationDefinition<unknown, NavigationParams>>>,
74
+ > = {
75
+ [Name in keyof Routes]: Readonly<{
76
+ name: Name
77
+ target: ValueOf<Routes[Name]['target']>
78
+ params: Readonly<{
79
+ [Param in keyof Routes[Name]['params']]: ValueOf<Routes[Name]['params'][Param]>
80
+ }>
81
+ }>
82
+ }[keyof Routes]
83
+
84
+ export type NavigationRoutes = Readonly<
85
+ Record<string, LocationDefinition<unknown, NavigationParams>>
86
+ >
87
+
88
+ export interface NavigationDefinition<Routes extends NavigationRoutes = NavigationRoutes> {
89
+ readonly routes: Routes
90
+ encode(target: RouteTarget<Routes>): SerializedLocation
91
+ decode(location: SerializedLocation): RouteTarget<Routes> | null
92
+ admit(location: SerializedLocation): SerializedLocation | null
93
+ }
94
+
95
+ export function defineNavigation<const Routes extends NavigationRoutes>(
96
+ routes: Routes,
97
+ ): NavigationDefinition<Routes> {
98
+ const names = Object.keys(routes).sort() as (keyof Routes)[]
99
+ const byView = new Map(names.map((name) => [routes[name]!.view, name] as const))
100
+ if (byView.size !== names.length) throw new TypeError('Navigation Views must be unique.')
101
+ const definition: NavigationDefinition<Routes> = {
102
+ routes: Object.freeze({ ...routes }),
103
+ encode(target) {
104
+ const route = routes[target.name]
105
+ if (route === undefined)
106
+ throw new TypeError(`Unknown navigation location '${String(target.name)}'.`)
107
+ const params = Object.fromEntries(
108
+ Object.entries(route.params).map(([name, codec]) => [
109
+ name,
110
+ codec.encode((target.params as Record<string, unknown>)[name]),
111
+ ]),
112
+ )
113
+ return Object.freeze({
114
+ node: Path.parse(route.target.encode(target.target)).raw,
115
+ view: route.view,
116
+ ...(Object.keys(params).length === 0 ? {} : { params: Object.freeze(params) }),
117
+ })
118
+ },
119
+ decode(serialized) {
120
+ if (serialized.view === undefined) return null
121
+ const name = byView.get(serialized.view)
122
+ if (name === undefined) return null
123
+ const route = routes[name]!
124
+ try {
125
+ const params = Object.fromEntries(
126
+ Object.entries(route.params).map(([param, codec]) => [
127
+ param,
128
+ codec.decode(serialized.params?.[param]),
129
+ ]),
130
+ )
131
+ if (Object.keys(serialized.params ?? {}).some((param) => !(param in route.params)))
132
+ return null
133
+ return Object.freeze({
134
+ name,
135
+ target: route.target.decode(serialized.node),
136
+ params: Object.freeze(params),
137
+ }) as RouteTarget<Routes>
138
+ } catch {
139
+ return null
140
+ }
141
+ },
142
+ admit(serialized) {
143
+ const decoded = definition.decode(serialized)
144
+ return decoded === null ? null : definition.encode(decoded)
145
+ },
146
+ }
147
+ return Object.freeze(definition)
148
+ }
149
+
150
+ export function NavigationScope<Routes extends NavigationRoutes>(props: {
151
+ readonly navigation: NavigationDefinition<Routes>
152
+ readonly adapter?: NavAdapter
153
+ readonly restore?: NavSnapshot
154
+ readonly initial?: RouteTarget<Routes>
155
+ readonly children: ReactNode
156
+ }): ReactElement {
157
+ return createElement(NavScope, {
158
+ ...(props.adapter === undefined ? {} : { adapter: props.adapter }),
159
+ ...(props.restore === undefined ? {} : { restore: props.restore }),
160
+ ...(props.initial === undefined ? {} : { initial: props.navigation.encode(props.initial) }),
161
+ admit: props.navigation.admit,
162
+ children: props.children,
163
+ })
164
+ }
165
+
166
+ export function useNavigation<Routes extends NavigationRoutes>(
167
+ navigation: NavigationDefinition<Routes>,
168
+ ) {
169
+ const context = useNavCtx()
170
+ useHistoryVersion(context.history)
171
+ const current = context.history.current
172
+ return Object.freeze({
173
+ location: current === null ? null : navigation.decode(current),
174
+ push: (target: RouteTarget<Routes>) => context.navigate(navigation.encode(target)),
175
+ replace: (target: RouteTarget<Routes>) =>
176
+ context.navigate(navigation.encode(target), { replace: true }),
177
+ back: context.navigate.back,
178
+ forward: context.navigate.forward,
179
+ snapshot: () => context.history.snapshot(),
180
+ })
181
+ }
182
+
183
+ export type NavigationTarget<Definition> =
184
+ Definition extends NavigationDefinition<infer Routes> ? RouteTarget<Routes> : never
185
+
186
+ function viewKey(input: string): Key.View {
187
+ const key = admitKey(input)
188
+ if (!admitKey.ref(key).startsWith('view.')) throw new TypeError(`Invalid View key '${input}'.`)
189
+ return key as Key.View
190
+ }
@@ -4,19 +4,21 @@
4
4
  * (`SerializedLocation`/`NavSnapshot` + equality/clone), and the seed precedence.
5
5
  * Source of truth for back/forward; the adapter is a projection sink around it.
6
6
  */
7
+ import type { NodePath } from '@astrale-os/sdk/graph/path'
8
+ import type { Key } from '@astrale-os/sdk/schema'
7
9
 
8
10
  /** The serializable projection persisted in history / URLs / snapshots. */
9
11
  export interface SerializedLocation {
10
- node: string
11
- view?: string
12
- params?: Record<string, string>
12
+ readonly node: NodePath
13
+ readonly view?: Key.View
14
+ readonly params?: Readonly<Record<string, string>>
13
15
  }
14
16
 
15
17
  /** A versioned history snapshot — round-trips through `NavScope.restore`. */
16
18
  export interface NavSnapshot {
17
- v: 1
18
- entries: SerializedLocation[]
19
- cursor: number
19
+ readonly v: 1
20
+ readonly entries: readonly SerializedLocation[]
21
+ readonly cursor: number
20
22
  }
21
23
 
22
24
  /** Structural equality of two serialized locations (node + view + params). */
@@ -24,7 +26,10 @@ export function sameLocation(a: SerializedLocation, b: SerializedLocation): bool
24
26
  return a.node === b.node && a.view === b.view && sameParams(a.params, b.params)
25
27
  }
26
28
 
27
- function sameParams(a?: Record<string, string>, b?: Record<string, string>): boolean {
29
+ function sameParams(
30
+ a?: Readonly<Record<string, string>>,
31
+ b?: Readonly<Record<string, string>>,
32
+ ): boolean {
28
33
  if (a === b) return true
29
34
  const ak = a ? Object.keys(a) : []
30
35
  const bk = b ? Object.keys(b) : []
@@ -74,7 +79,11 @@ export class NavHistory {
74
79
  }
75
80
 
76
81
  snapshot(): NavSnapshot {
77
- return { v: 1, entries: this.entries.map(cloneLocation), cursor: this.cursor }
82
+ return Object.freeze({
83
+ v: 1 as const,
84
+ entries: Object.freeze(this.entries.map(cloneLocation)),
85
+ cursor: this.cursor,
86
+ })
78
87
  }
79
88
 
80
89
  /** Push (or replace the current entry with) a location. No-op on same location. */
@@ -129,8 +138,8 @@ export class NavHistory {
129
138
  }
130
139
 
131
140
  /** Replace the whole stack (restore revalidation prune). */
132
- reset(entries: SerializedLocation[], cursor: number): void {
133
- this.entries = entries
141
+ reset(entries: readonly SerializedLocation[], cursor: number): void {
142
+ this.entries = [...entries]
134
143
  this.cursor = cursor
135
144
  this.emit()
136
145
  }
@@ -151,7 +160,7 @@ export class NavHistory {
151
160
  */
152
161
  export function seedEntries(
153
162
  fromAdapter: SerializedLocation | null,
154
- restore: { entries: SerializedLocation[]; cursor: number } | undefined,
163
+ restore: { readonly entries: readonly SerializedLocation[]; readonly cursor: number } | undefined,
155
164
  initialSerialized: SerializedLocation | null,
156
165
  ): { entries: SerializedLocation[]; cursor: number } {
157
166
  if (restore !== undefined && restore.entries.length > 0) {
@@ -1,71 +1,21 @@
1
- import type { PathLike } from '@astrale-os/kernel-client/graph'
2
- import type { LiveResult, ShapeInput } from '@astrale-os/kernel-client/store'
3
-
4
- /**
5
- * useAncestors (API §8.2) — the live, inclusive root-first ancestor chain of a
6
- * node (a move re-renders it). Defaults to the nearest scope's location node;
7
- * passing a node/path makes it usable outside a scope. Drops any masked ancestor.
8
- */
9
- import { useMemo } from 'react'
1
+ import type { Node } from '@astrale-os/sdk/graph/node'
2
+ import type { PathLike } from '@astrale-os/sdk/graph/path'
10
3
 
11
4
  import type { ReadState } from '../../graph/memory/read/envelope.js'
12
- import type { Node } from '../location.js'
13
5
 
14
- import { idleReadState } from '../../graph/memory/read/idle.js'
15
- import { useMemory } from '../../graph/memory/scope.js'
16
- import { chainPaths, pathHint, pathOf } from '../location.js'
17
- import { useHistoryVersion, useOptionalNavCtx, useMemoryShape } from '../scope.context.js'
18
- import { serializeTarget } from '../snapshot.js'
6
+ import { unsupportedReadState } from '../../graph/memory/read/envelope.js'
7
+
8
+ const EMPTY_CHAIN: readonly Node[] = Object.freeze([])
19
9
 
20
10
  /**
21
- * The root-first ancestor chain (inclusive of the node itself), live: a move
22
- * re-renders it. `node` defaults to the nearest scope's location node; passing a
23
- * node/path makes it usable outside a scope. The chain drops any ancestor whose
24
- * record is masked/absent.
11
+ * Retained public concern with an explicit failed state: V2 has no implicit
12
+ * slash-tree ancestry. Callers must query a declared Edge Class instead.
25
13
  */
26
14
  export function useAncestors(
27
- node?: Node | PathLike,
15
+ _node?: Node | PathLike,
28
16
  ): ReadState & { readonly chain: readonly Node[] } {
29
- const memory = useMemory()
30
- const ctx = useOptionalNavCtx()
31
- useHistoryVersion(ctx?.history ?? null)
32
-
33
- const baseRef =
34
- node !== undefined ? serializeTarget(node).node : (ctx?.history.current?.node ?? null)
35
- const baseResult = useMemoryShape(memory, baseRef)
36
- const basePath = baseResult?.node ? pathOf(baseResult.node) : pathHint(baseRef ?? '')
37
-
38
- const paths = useMemo(() => (basePath !== '' ? chainPaths(basePath) : EMPTY_PATHS), [basePath])
39
- const shape = useMemo<ShapeInput | null>(
40
- () => (basePath !== '' ? (q) => q.from(...chainPaths(basePath)) : null),
41
- [basePath],
42
- )
43
- const chainResult = useMemoryShape(memory, shape)
44
- const chain = useMemo<readonly Node[]>(() => {
45
- if (chainResult === null) return EMPTY_CHAIN
46
- const byPath = new Map<string, Node>()
47
- for (const n of chainResult.roots) byPath.set(pathOf(n), n)
48
- const out: Node[] = []
49
- for (const p of paths) {
50
- const hit = byPath.get(p)
51
- if (hit !== undefined) out.push(hit)
52
- }
53
- return out
54
- }, [paths, chainResult])
55
-
56
- return { ...readStateOf(chainResult), chain }
57
- }
58
-
59
- const EMPTY_PATHS: readonly string[] = Object.freeze([])
60
- const EMPTY_CHAIN: readonly Node[] = Object.freeze([])
61
-
62
- function readStateOf(result: LiveResult | null): ReadState {
63
- if (result === null) return idleReadState()
64
17
  return {
65
- pending: result.pending,
66
- error: result.error,
67
- live: result.live,
68
- seq: result.seq,
69
- refetch: result.refetch,
18
+ ...unsupportedReadState('navigation.ancestors requires an explicit parent Edge Class'),
19
+ chain: EMPTY_CHAIN,
70
20
  }
71
21
  }
@@ -6,16 +6,14 @@ import { useMemo } from 'react'
6
6
 
7
7
  import type { NavLocation } from '../location.js'
8
8
 
9
- import { buildLocation, useSettled } from '../location.js'
10
- import { useHistoryVersion, useNavCtx, useMemoryShape } from '../scope.context.js'
9
+ import { buildLocation } from '../location.js'
10
+ import { useGraphPoint, useHistoryVersion, useNavCtx } from '../scope.context.js'
11
11
 
12
12
  /** The nearest scope's live location. Throws outside any {@link NavScope}. */
13
13
  export function useLocation(): NavLocation {
14
14
  const ctx = useNavCtx()
15
15
  useHistoryVersion(ctx.history)
16
16
  const entry = ctx.history.current
17
- const shape = entry?.node ?? null
18
- const result = useMemoryShape(ctx.memory, shape)
19
- const settled = useSettled(result, shape)
20
- return useMemo(() => buildLocation(entry, result, settled), [entry, result, settled])
17
+ const result = useGraphPoint(entry?.node ?? null)
18
+ return useMemo(() => buildLocation(entry, result), [entry, result])
21
19
  }