@astrale-os/shell-react 0.2.9 → 0.2.10-beta.3

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 (335) hide show
  1. package/dist/application/apps/app-host.d.ts +4 -15
  2. package/dist/application/apps/app-host.d.ts.map +1 -1
  3. package/dist/application/apps/app-host.js +1 -1
  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/astrale.d.ts +14 -0
  39. package/dist/components/astrale.d.ts.map +1 -0
  40. package/dist/components/astrale.js +12 -0
  41. package/dist/components/boundaries.d.ts +47 -0
  42. package/dist/components/boundaries.d.ts.map +1 -0
  43. package/dist/components/boundaries.js +55 -0
  44. package/dist/components/can.d.ts +11 -0
  45. package/dist/components/can.d.ts.map +1 -0
  46. package/dist/components/can.js +5 -0
  47. package/dist/components/index.d.ts +6 -0
  48. package/dist/components/index.d.ts.map +1 -0
  49. package/dist/components/index.js +3 -0
  50. package/dist/graph/call/call.hook.d.ts +8 -18
  51. package/dist/graph/call/call.hook.d.ts.map +1 -1
  52. package/dist/graph/call/call.hook.js +7 -13
  53. package/dist/graph/call/stream.hook.d.ts +12 -20
  54. package/dist/graph/call/stream.hook.d.ts.map +1 -1
  55. package/dist/graph/call/stream.hook.js +55 -111
  56. package/dist/graph/graph.context.d.ts +4 -3
  57. package/dist/graph/graph.context.d.ts.map +1 -1
  58. package/dist/graph/graph.context.js +2 -8
  59. package/dist/graph/guards.error.d.ts +7 -8
  60. package/dist/graph/guards.error.d.ts.map +1 -1
  61. package/dist/graph/guards.error.js +11 -7
  62. package/dist/graph/index.d.ts +8 -16
  63. package/dist/graph/index.d.ts.map +1 -1
  64. package/dist/graph/index.js +3 -7
  65. package/dist/graph/memory/read/envelope.d.ts +22 -54
  66. package/dist/graph/memory/read/envelope.d.ts.map +1 -1
  67. package/dist/graph/memory/read/envelope.js +26 -20
  68. package/dist/graph/memory/read/idle.d.ts +1 -15
  69. package/dist/graph/memory/read/idle.d.ts.map +1 -1
  70. package/dist/graph/memory/read/idle.js +4 -13
  71. package/dist/graph/query-definition.hook.d.ts +11 -0
  72. package/dist/graph/query-definition.hook.d.ts.map +1 -0
  73. package/dist/graph/query-definition.hook.js +205 -0
  74. package/dist/graph/resource.d.ts +34 -0
  75. package/dist/graph/resource.d.ts.map +1 -0
  76. package/dist/graph/resource.js +244 -0
  77. package/dist/graph/store.d.ts +25 -0
  78. package/dist/graph/store.d.ts.map +1 -0
  79. package/dist/graph/store.js +69 -0
  80. package/dist/host/actions.hook.d.ts +34 -0
  81. package/dist/host/actions.hook.d.ts.map +1 -0
  82. package/dist/host/actions.hook.js +125 -0
  83. package/dist/host/components.d.ts +15 -0
  84. package/dist/host/components.d.ts.map +1 -0
  85. package/dist/host/components.js +14 -0
  86. package/dist/host/index.d.ts +5 -0
  87. package/dist/host/index.d.ts.map +1 -0
  88. package/dist/host/index.js +3 -0
  89. package/dist/index.d.ts +26 -37
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/index.js +20 -31
  92. package/dist/intents/envelope.d.ts +7 -19
  93. package/dist/intents/envelope.d.ts.map +1 -1
  94. package/dist/intents/envelope.js +2 -3
  95. package/dist/intents/send-intent.hook.d.ts +5 -7
  96. package/dist/intents/send-intent.hook.d.ts.map +1 -1
  97. package/dist/intents/send-intent.hook.js +17 -75
  98. package/dist/navigation/adapters/adapter.d.ts +5 -0
  99. package/dist/navigation/adapters/adapter.d.ts.map +1 -1
  100. package/dist/navigation/adapters/memory.adapter.d.ts.map +1 -1
  101. package/dist/navigation/adapters/memory.adapter.js +4 -0
  102. package/dist/navigation/adapters/url.adapter.d.ts.map +1 -1
  103. package/dist/navigation/adapters/url.adapter.js +57 -10
  104. package/dist/navigation/codec.d.ts +60 -0
  105. package/dist/navigation/codec.d.ts.map +1 -0
  106. package/dist/navigation/codec.js +121 -0
  107. package/dist/navigation/history.d.ts +11 -9
  108. package/dist/navigation/history.d.ts.map +1 -1
  109. package/dist/navigation/history.js +6 -8
  110. package/dist/navigation/hooks/ancestors.hook.d.ts +5 -7
  111. package/dist/navigation/hooks/ancestors.hook.d.ts.map +1 -1
  112. package/dist/navigation/hooks/ancestors.hook.js +7 -51
  113. package/dist/navigation/hooks/location.hook.d.ts.map +1 -1
  114. package/dist/navigation/hooks/location.hook.js +4 -6
  115. package/dist/navigation/hooks/scope.hook.d.ts.map +1 -1
  116. package/dist/navigation/hooks/scope.hook.js +4 -6
  117. package/dist/navigation/index.d.ts +4 -2
  118. package/dist/navigation/index.d.ts.map +1 -1
  119. package/dist/navigation/index.js +2 -1
  120. package/dist/navigation/link.d.ts +12 -0
  121. package/dist/navigation/link.d.ts.map +1 -1
  122. package/dist/navigation/link.js +13 -2
  123. package/dist/navigation/location.d.ts +13 -40
  124. package/dist/navigation/location.d.ts.map +1 -1
  125. package/dist/navigation/location.js +14 -76
  126. package/dist/navigation/scope.context.d.ts +17 -26
  127. package/dist/navigation/scope.context.d.ts.map +1 -1
  128. package/dist/navigation/scope.context.js +140 -108
  129. package/dist/navigation/snapshot.d.ts +14 -19
  130. package/dist/navigation/snapshot.d.ts.map +1 -1
  131. package/dist/navigation/snapshot.js +48 -44
  132. package/dist/schema/action.hook.d.ts +44 -0
  133. package/dist/schema/action.hook.d.ts.map +1 -0
  134. package/dist/schema/action.hook.js +72 -0
  135. package/dist/schema/domain.hook.d.ts +12 -7
  136. package/dist/schema/domain.hook.d.ts.map +1 -1
  137. package/dist/schema/domain.hook.js +101 -4
  138. package/dist/schema/index.d.ts +9 -8
  139. package/dist/schema/index.d.ts.map +1 -1
  140. package/dist/schema/index.js +5 -7
  141. package/dist/schema/kernel.hook.d.ts +4 -9
  142. package/dist/schema/kernel.hook.d.ts.map +1 -1
  143. package/dist/schema/kernel.hook.js +4 -6
  144. package/dist/schema/mutation.hook.d.ts +9 -0
  145. package/dist/schema/mutation.hook.d.ts.map +1 -0
  146. package/dist/schema/mutation.hook.js +41 -0
  147. package/dist/schema/object.hook.d.ts +16 -0
  148. package/dist/schema/object.hook.d.ts.map +1 -0
  149. package/dist/schema/object.hook.js +46 -0
  150. package/dist/schema/relation.hook.d.ts +46 -0
  151. package/dist/schema/relation.hook.d.ts.map +1 -0
  152. package/dist/schema/relation.hook.js +136 -0
  153. package/dist/selection/live.d.ts +9 -17
  154. package/dist/selection/live.d.ts.map +1 -1
  155. package/dist/selection/live.js +65 -112
  156. package/dist/selection/scope.context.js +5 -4
  157. package/dist/session/boot/boot.d.ts +1 -13
  158. package/dist/session/boot/boot.d.ts.map +1 -1
  159. package/dist/session/boot/boot.js +9 -27
  160. package/dist/session/index.d.ts +1 -0
  161. package/dist/session/index.d.ts.map +1 -1
  162. package/dist/session/kernel-instance.hook.d.ts +1 -4
  163. package/dist/session/kernel-instance.hook.d.ts.map +1 -1
  164. package/dist/session/kernel-instance.hook.js +1 -5
  165. package/dist/session/provider.d.ts +9 -23
  166. package/dist/session/provider.d.ts.map +1 -1
  167. package/dist/session/provider.js +29 -75
  168. package/dist/session/session.context.d.ts +5 -0
  169. package/dist/session/session.context.d.ts.map +1 -1
  170. package/dist/window/adopt.d.ts +0 -1
  171. package/dist/window/adopt.d.ts.map +1 -1
  172. package/dist/window/hooks/target-node.hook.d.ts +10 -6
  173. package/dist/window/hooks/target-node.hook.d.ts.map +1 -1
  174. package/dist/window/hooks/target-node.hook.js +14 -72
  175. package/dist/window/hooks/views-for.hook.d.ts +4 -3
  176. package/dist/window/hooks/views-for.hook.d.ts.map +1 -1
  177. package/dist/window/hooks/views-for.hook.js +6 -22
  178. package/dist/window/hooks/windows.hook.d.ts.map +1 -1
  179. package/dist/window/hooks/windows.hook.js +3 -4
  180. package/dist/window/host.d.ts +9 -11
  181. package/dist/window/host.d.ts.map +1 -1
  182. package/dist/window/host.js +26 -62
  183. package/dist/window/hub.d.ts +2 -0
  184. package/dist/window/hub.d.ts.map +1 -1
  185. package/dist/window/hub.js +33 -0
  186. package/dist/window/index.d.ts +2 -2
  187. package/dist/window/index.d.ts.map +1 -1
  188. package/dist/window/index.js +2 -2
  189. package/dist/window/lifecycle/mount.d.ts +5 -44
  190. package/dist/window/lifecycle/mount.d.ts.map +1 -1
  191. package/dist/window/lifecycle/mount.js +14 -120
  192. package/dist/window/lifecycle/states.d.ts +0 -5
  193. package/dist/window/lifecycle/states.d.ts.map +1 -1
  194. package/package.json +20 -8
  195. package/src/application/apps/app-host.tsx +11 -15
  196. package/src/application/apps/apps.hook.ts +13 -6
  197. package/src/application/apps/domains.hook.ts +1 -1
  198. package/src/application/apps/open-app.hook.ts +3 -4
  199. package/src/application/index.ts +4 -6
  200. package/src/application/spaces/space.hook.ts +5 -6
  201. package/src/application/spaces/spaces.hook.ts +14 -9
  202. package/src/auth/act-as.context.tsx +37 -91
  203. package/src/auth/auth.hook.ts +2 -30
  204. package/src/auth/can.hook.ts +44 -135
  205. package/src/auth/index.ts +1 -0
  206. package/src/auth/self.hook.ts +2 -9
  207. package/src/auth/session-kernel.ts +6 -16
  208. package/src/components/astrale.tsx +40 -0
  209. package/src/components/boundaries.tsx +113 -0
  210. package/src/components/can.tsx +16 -0
  211. package/src/components/index.ts +13 -0
  212. package/src/graph/call/call.hook.ts +22 -27
  213. package/src/graph/call/stream.hook.ts +66 -143
  214. package/src/graph/graph.context.ts +6 -11
  215. package/src/graph/guards.error.ts +17 -55
  216. package/src/graph/index.ts +23 -17
  217. package/src/graph/memory/read/envelope.ts +40 -65
  218. package/src/graph/memory/read/idle.ts +5 -18
  219. package/src/graph/query-definition.hook.ts +270 -0
  220. package/src/graph/resource.ts +358 -0
  221. package/src/graph/store.ts +97 -0
  222. package/src/host/actions.hook.ts +165 -0
  223. package/src/host/components.tsx +29 -0
  224. package/src/host/index.ts +17 -0
  225. package/src/index.ts +131 -124
  226. package/src/intents/envelope.ts +7 -23
  227. package/src/intents/send-intent.hook.ts +24 -79
  228. package/src/navigation/adapters/adapter.ts +3 -0
  229. package/src/navigation/adapters/memory.adapter.ts +4 -0
  230. package/src/navigation/adapters/url.adapter.ts +48 -8
  231. package/src/navigation/codec.ts +190 -0
  232. package/src/navigation/history.ts +20 -11
  233. package/src/navigation/hooks/ancestors.hook.ts +10 -60
  234. package/src/navigation/hooks/location.hook.ts +4 -6
  235. package/src/navigation/hooks/scope.hook.ts +4 -6
  236. package/src/navigation/index.ts +18 -2
  237. package/src/navigation/link.tsx +27 -2
  238. package/src/navigation/location.ts +26 -93
  239. package/src/navigation/scope.context.tsx +151 -136
  240. package/src/navigation/snapshot.ts +69 -50
  241. package/src/schema/action.hook.ts +146 -0
  242. package/src/schema/domain.hook.ts +127 -11
  243. package/src/schema/index.ts +9 -30
  244. package/src/schema/kernel.hook.ts +6 -13
  245. package/src/schema/mutation.hook.ts +63 -0
  246. package/src/schema/object.hook.ts +85 -0
  247. package/src/schema/relation.hook.ts +253 -0
  248. package/src/selection/live.ts +75 -125
  249. package/src/selection/scope.context.tsx +4 -4
  250. package/src/session/boot/boot.ts +8 -33
  251. package/src/session/index.ts +1 -0
  252. package/src/session/kernel-instance.hook.ts +2 -11
  253. package/src/session/provider.tsx +34 -103
  254. package/src/session/session.context.ts +6 -0
  255. package/src/window/adopt.ts +0 -1
  256. package/src/window/hooks/target-node.hook.ts +20 -85
  257. package/src/window/hooks/views-for.hook.ts +12 -25
  258. package/src/window/hooks/windows.hook.ts +3 -4
  259. package/src/window/host.tsx +35 -77
  260. package/src/window/hub.ts +33 -0
  261. package/src/window/index.ts +3 -3
  262. package/src/window/lifecycle/mount.ts +15 -160
  263. package/src/window/lifecycle/states.ts +1 -5
  264. package/dist/application/roles.hook.d.ts +0 -7
  265. package/dist/application/roles.hook.d.ts.map +0 -1
  266. package/dist/application/roles.hook.js +0 -23
  267. package/dist/graph/memory/base.d.ts +0 -21
  268. package/dist/graph/memory/base.d.ts.map +0 -1
  269. package/dist/graph/memory/base.js +0 -33
  270. package/dist/graph/memory/read/read.d.ts +0 -9
  271. package/dist/graph/memory/read/read.d.ts.map +0 -1
  272. package/dist/graph/memory/read/read.js +0 -87
  273. package/dist/graph/memory/scope.d.ts +0 -7
  274. package/dist/graph/memory/scope.d.ts.map +0 -1
  275. package/dist/graph/memory/scope.js +0 -25
  276. package/dist/graph/mutate/move.hook.d.ts +0 -7
  277. package/dist/graph/mutate/move.hook.d.ts.map +0 -1
  278. package/dist/graph/mutate/move.hook.js +0 -49
  279. package/dist/graph/mutate/mutate.hook.d.ts +0 -17
  280. package/dist/graph/mutate/mutate.hook.d.ts.map +0 -1
  281. package/dist/graph/mutate/mutate.hook.js +0 -45
  282. package/dist/graph/query/children.hook.d.ts +0 -29
  283. package/dist/graph/query/children.hook.d.ts.map +0 -1
  284. package/dist/graph/query/children.hook.js +0 -64
  285. package/dist/graph/query/graph.hook.d.ts +0 -9
  286. package/dist/graph/query/graph.hook.d.ts.map +0 -1
  287. package/dist/graph/query/graph.hook.js +0 -5
  288. package/dist/graph/query/neighbors.hook.d.ts +0 -7
  289. package/dist/graph/query/neighbors.hook.d.ts.map +0 -1
  290. package/dist/graph/query/neighbors.hook.js +0 -104
  291. package/dist/graph/query/node.hook.d.ts +0 -14
  292. package/dist/graph/query/node.hook.d.ts.map +0 -1
  293. package/dist/graph/query/node.hook.js +0 -43
  294. package/dist/graph/query/query.hook.d.ts +0 -14
  295. package/dist/graph/query/query.hook.d.ts.map +0 -1
  296. package/dist/graph/query/query.hook.js +0 -23
  297. package/dist/schema/bind-read.d.ts +0 -37
  298. package/dist/schema/bind-read.d.ts.map +0 -1
  299. package/dist/schema/bind-read.js +0 -205
  300. package/dist/schema/binding.d.ts +0 -21
  301. package/dist/schema/binding.d.ts.map +0 -1
  302. package/dist/schema/binding.js +0 -99
  303. package/dist/schema/function.hook.d.ts +0 -9
  304. package/dist/schema/function.hook.d.ts.map +0 -1
  305. package/dist/schema/function.hook.js +0 -40
  306. package/dist/schema/method.hook.d.ts +0 -14
  307. package/dist/schema/method.hook.d.ts.map +0 -1
  308. package/dist/schema/method.hook.js +0 -49
  309. package/dist/schema/tag.d.ts +0 -11
  310. package/dist/schema/tag.d.ts.map +0 -1
  311. package/dist/schema/tag.js +0 -11
  312. package/dist/schema/types.d.ts +0 -73
  313. package/dist/schema/types.d.ts.map +0 -1
  314. package/dist/schema/types.js +0 -1
  315. package/dist/schema/write.d.ts +0 -16
  316. package/dist/schema/write.d.ts.map +0 -1
  317. package/dist/schema/write.js +0 -56
  318. package/src/application/roles.hook.ts +0 -41
  319. package/src/graph/memory/base.ts +0 -50
  320. package/src/graph/memory/read/read.ts +0 -103
  321. package/src/graph/memory/scope.ts +0 -29
  322. package/src/graph/mutate/move.hook.ts +0 -70
  323. package/src/graph/mutate/mutate.hook.ts +0 -69
  324. package/src/graph/query/children.hook.ts +0 -136
  325. package/src/graph/query/graph.hook.ts +0 -13
  326. package/src/graph/query/neighbors.hook.ts +0 -159
  327. package/src/graph/query/node.hook.ts +0 -83
  328. package/src/graph/query/query.hook.ts +0 -42
  329. package/src/schema/bind-read.ts +0 -319
  330. package/src/schema/binding.ts +0 -146
  331. package/src/schema/function.hook.ts +0 -68
  332. package/src/schema/method.hook.ts +0 -103
  333. package/src/schema/tag.ts +0 -19
  334. package/src/schema/types.ts +0 -150
  335. package/src/schema/write.ts +0 -96
@@ -1,14 +1,9 @@
1
- import type { GraphMemory, LiveResult, ShapeInput } from '@astrale-os/kernel-client/store'
1
+ import type { GraphStore, StoreQueryResponse } from '@astrale-os/kernel-client/store'
2
2
  import type { ReactElement, ReactNode } from 'react'
3
3
 
4
- /**
5
- * NavScope (API §8.2) — owns one history stack, wires it to an adapter, and seats
6
- * the scope context. Seeds from restore/adapter/initial, revalidates a restored
7
- * stack against the memory, mirrors navigations to/from the adapter, and runs the
8
- * `missing` policy when the current anchor stops resolving. Also the scope's shared
9
- * glue (`NavContext`, the `useSyncExternalStore` reads) and the imperative
10
- * `Navigate` surface. Scopes nest.
11
- */
4
+ import { NodePath } from '@astrale-os/sdk/graph/path'
5
+ import { Query } from '@astrale-os/sdk/query'
6
+ import { Key } from '@astrale-os/sdk/schema'
12
7
  import {
13
8
  createContext,
14
9
  startTransition,
@@ -22,40 +17,40 @@ import {
22
17
 
23
18
  import type { NavAdapter } from './adapters/adapter.js'
24
19
  import type { NavSnapshot, SerializedLocation } from './history.js'
25
- import type { NavLocation } from './location.js'
20
+ import type { NavLocation, PointResult } from './location.js'
26
21
  import type { NavTarget } from './snapshot.js'
27
22
 
28
- import { useMemory } from '../graph/memory/scope.js'
23
+ import { UnsupportedFeatureError } from '../graph/memory/read/envelope.js'
24
+ import { useStoreResource } from '../graph/resource.js'
25
+ import { useGraphStore } from '../graph/store.js'
26
+ import { useShell } from '../session/shell.hook.js'
29
27
  import { memoryAdapter } from './adapters/memory.adapter.js'
30
28
  import { NavHistory, seedEntries } from './history.js'
31
- import { buildLocation, parentPath, pathHint, pathOf, useSettled } from './location.js'
29
+ import { buildLocation } from './location.js'
32
30
  import { revalidateAndPrune, serializeTarget } from './snapshot.js'
33
31
 
34
- /** The imperative navigation surface (API §8.2). */
35
32
  export interface Navigate {
36
33
  (to: NavTarget, opts?: { replace?: boolean }): void
37
34
  back(): void
38
35
  forward(): void
39
- /** To the parent via `has_parent`; no-op at root or while the node is cold. */
40
- up(): void
36
+ /** Unsupported until the caller supplies an explicit parent Edge Class. */
37
+ up(): never
41
38
  readonly canBack: boolean
42
39
  readonly canForward: boolean
43
40
  }
44
41
 
45
- /** A mutable `Navigate` — the scope stamps `canBack`/`canForward` onto it live. */
46
42
  export interface MutableNavigate {
47
43
  (to: Parameters<Navigate>[0], opts?: Parameters<Navigate>[1]): void
48
44
  back(): void
49
45
  forward(): void
50
- up(): void
46
+ up(): never
51
47
  canBack: boolean
52
48
  canForward: boolean
53
49
  }
54
50
 
55
- /** The nearest scope's shared handle (stable for the scope's lifetime). */
56
51
  export interface NavCtx {
57
52
  readonly history: NavHistory
58
- readonly memory: GraphMemory
53
+ readonly store: GraphStore
59
54
  readonly adapter: NavAdapter
60
55
  readonly navigate: MutableNavigate
61
56
  }
@@ -63,205 +58,225 @@ export interface NavCtx {
63
58
  export const NavContext = createContext<NavCtx | null>(null)
64
59
  NavContext.displayName = 'AstraleNavContext'
65
60
 
66
- /** The nearest scope, or throw — the door every public nav hook goes through. */
67
61
  export function useNavCtx(): NavCtx {
68
62
  const ctx = useContext(NavContext)
69
63
  if (ctx === null) throw new Error('nav hooks must be used within a <NavScope>')
70
64
  return ctx
71
65
  }
72
66
 
73
- /** The nearest scope or null (for `useAncestors`, usable outside a scope). */
74
67
  export function useOptionalNavCtx(): NavCtx | null {
75
68
  return useContext(NavContext)
76
69
  }
77
70
 
78
71
  const NOOP_UNSUB = (): void => undefined
79
72
 
80
- /** Re-render on any history change (no-op subscription when there is no scope). */
81
73
  export function useHistoryVersion(history: NavHistory | null): number {
82
74
  const subscribe = useCallback(
83
- (cb: () => void) => (history !== null ? history.subscribe(cb) : NOOP_UNSUB),
75
+ (listener: () => void) => (history === null ? NOOP_UNSUB : history.subscribe(listener)),
84
76
  [history],
85
77
  )
86
- const getSnapshot = useCallback(() => (history !== null ? history.getVersion() : 0), [history])
78
+ const getSnapshot = useCallback(() => (history === null ? 0 : history.getVersion()), [history])
87
79
  return useSyncExternalStore(subscribe, getSnapshot, getSnapshot)
88
80
  }
89
81
 
90
- /**
91
- * Subscribe to one memory shape, tear-free. `shape` must be stable per read
92
- * (a string ref, or a builder memoized upstream); a null shape is idle (no I/O).
93
- */
94
- export function useMemoryShape(memory: GraphMemory, shape: ShapeInput | null): LiveResult | null {
95
- const subscribe = useCallback(
96
- (cb: () => void) => (shape !== null ? memory.subscribe(shape, cb) : NOOP_UNSUB),
97
- [memory, shape],
82
+ /** Caller-scoped canonical point read through the provider's one GraphStore. */
83
+ export function useGraphPoint(target: NodePath | null): PointResult | null {
84
+ const query = useMemo(
85
+ () =>
86
+ target === null
87
+ ? null
88
+ : Query.from({ kind: 'node', paths: [target], binding: 'navigation_target' }).select({
89
+ kind: 'graph',
90
+ }),
91
+ [target],
98
92
  )
99
- const getSnapshot = useCallback(
100
- () => (shape !== null ? memory.getSnapshot(shape) : null),
101
- [memory, shape],
93
+ const project = useCallback(
94
+ (response: StoreQueryResponse<NonNullable<typeof query>>) => {
95
+ const result = response.result
96
+ if (result.kind !== 'graph' || result.selection.kind !== 'node') return null
97
+ const id = result.selection.ids[0]
98
+ return id === undefined ? null : (result.graph.nodes.find((node) => node.id === id) ?? null)
99
+ },
100
+ [query],
102
101
  )
103
- return useSyncExternalStore(subscribe, getSnapshot, getSnapshot)
102
+ const resource = useStoreResource(query, 1, project)
103
+ if (target === null) return null
104
+ return {
105
+ node: resource.data ?? null,
106
+ pending: resource.state === 'loading' || resource.state === 'refreshing',
107
+ error: resource.error,
108
+ live: false,
109
+ seq: 0,
110
+ refetch: resource.refresh,
111
+ }
104
112
  }
105
113
 
106
- /** The missing-anchor policy — a fallback strategy or a caller-supplied resolver. */
107
- export type MissingPolicy = 'ancestor' | 'hold' | ((loc: NavLocation) => NavTarget | null)
114
+ type MissingResolver = (location: NavLocation) => NavTarget | null
115
+ export type MissingPolicy = 'hold' | MissingResolver
108
116
 
109
117
  export interface NavScopeProps {
110
- /** Persistence seam. Default: a fresh {@link memoryAdapter} (captured once). */
111
118
  adapter?: NavAdapter
112
- /** A saved snapshot — revalidated against the memory; dead entries dropped. */
113
119
  restore?: NavSnapshot
114
- /** Seed target when neither `adapter.read()` nor `restore` yields one. */
115
120
  initial?: NavTarget
116
- /** Fires per navigation with the new location + snapshot — persist here. */
117
- onChange?: (loc: NavLocation, snapshot: NavSnapshot) => void
118
- /** What to do when the current anchor stops resolving. Default `'ancestor'`. */
121
+ onChange?: (location: NavLocation, snapshot: NavSnapshot) => void
122
+ /** Default `hold`; parent fallback requires an explicit caller relationship. */
119
123
  missing?: MissingPolicy
124
+ /** Closed codec admission applied before any history or adapter mutation. */
125
+ admit?: (location: SerializedLocation) => SerializedLocation | null
120
126
  children: ReactNode
121
127
  }
122
128
 
123
129
  export function NavScope(props: NavScopeProps): ReactElement {
124
- const memory = useMemory()
125
-
126
- // Adapter + history are captured ONCE — an inline `adapter={memoryAdapter()}`
127
- // (or a re-passed `initial`) must never rebuild the scope's state.
130
+ const store = useGraphStore()
131
+ const shell = useShell()
128
132
  const adapterRef = useRef<NavAdapter | null>(null)
129
133
  if (adapterRef.current === null) adapterRef.current = props.adapter ?? memoryAdapter()
130
134
  const adapter = adapterRef.current
135
+ if (adapter.kind === 'browser' && shell.hostCapabilities.navigation.history !== 'browser') {
136
+ throw new Error('Browser navigation is not granted to this Shell.')
137
+ }
138
+ const admitRef = useRef(props.admit)
139
+ admitRef.current = props.admit
140
+ const admit = (location: SerializedLocation | null): SerializedLocation | null => {
141
+ if (location === null) return null
142
+ const portable = admitLocation(location)
143
+ return admitRef.current?.(portable) ?? portable
144
+ }
131
145
 
132
146
  const historyRef = useRef<NavHistory | null>(null)
133
147
  if (historyRef.current === null) {
134
148
  const seed = seedEntries(
135
- adapter.read(),
136
- props.restore,
137
- props.initial !== undefined ? serializeTarget(props.initial) : null,
149
+ admit(adapter.read()),
150
+ props.restore === undefined || props.restore.v !== 1
151
+ ? undefined
152
+ : {
153
+ entries: props.restore.entries.flatMap((entry) => {
154
+ const accepted = admit(entry)
155
+ return accepted === null ? [] : [accepted]
156
+ }),
157
+ cursor: props.restore.cursor,
158
+ },
159
+ props.initial === undefined ? null : admit(serializeTarget(props.initial)),
138
160
  )
139
161
  historyRef.current = new NavHistory(seed.entries, seed.cursor)
140
162
  }
141
163
  const history = historyRef.current
142
-
143
- // Latest-value refs so the stable navigate + effects read current inputs.
144
- const memoryRef = useRef(memory)
145
- memoryRef.current = memory
146
164
  const onChangeRef = useRef(props.onChange)
147
165
  onChangeRef.current = props.onChange
148
166
  const missingRef = useRef(props.missing)
149
167
  missingRef.current = props.missing
168
+ const historyVersion = useHistoryVersion(history)
150
169
 
151
170
  const fireOnChange = useCallback((): void => {
152
- const cb = onChangeRef.current
153
- if (cb === undefined) return
154
- const entry = history.current
155
- const live = entry !== null ? memoryRef.current.peek(entry.node) : null
156
- cb(buildLocation(entry, live, (live?.node ?? null) !== null), history.snapshot())
171
+ onChangeRef.current?.(buildLocation(history.current, null), history.snapshot())
157
172
  }, [history])
158
173
 
159
- // The one stable navigate object; hooks re-stamp canBack/canForward onto it.
160
174
  const navigate = useMemo<MutableNavigate>(() => {
161
175
  const commit = (changed: boolean, replace: boolean): void => {
162
176
  if (!changed) return
163
- const loc = history.current
164
- if (loc !== null) adapter.write(loc, { replace })
177
+ const location = history.current
178
+ if (location !== null) adapter.write(location, { replace })
165
179
  fireOnChange()
166
180
  }
167
- const nav = ((to: NavTarget, opts?: { replace?: boolean }) => {
168
- const loc = serializeTarget(to)
169
- const replace = opts?.replace ?? false
170
- startTransition(() => commit(history.push(loc, replace), replace))
181
+ const nav = ((to: NavTarget, options?: { replace?: boolean }) => {
182
+ const replace = options?.replace ?? false
183
+ const location = admit(serializeTarget(to))
184
+ if (location === null) throw new TypeError('Navigation target was rejected by its codec.')
185
+ startTransition(() => commit(history.push(location, replace), replace))
171
186
  }) as MutableNavigate
172
187
  nav.back = () => startTransition(() => commit(history.back(), false))
173
188
  nav.forward = () => startTransition(() => commit(history.forward(), false))
174
- nav.up = () => {
175
- const entry = history.current
176
- if (entry === null) return
177
- const live = memoryRef.current.peek(entry.node)
178
- const raw = live?.node ? pathOf(live.node) : pathHint(entry.node)
179
- const parent = raw !== '' ? parentPath(raw) : null
180
- if (parent !== null) nav(parent)
189
+ nav.up = (): never => {
190
+ throw new UnsupportedFeatureError('navigation.up requires an explicit parent Edge Class')
181
191
  }
182
192
  nav.canBack = history.canBack
183
193
  nav.canForward = history.canForward
184
194
  return nav
185
- }, [history, adapter, fireOnChange])
186
-
187
- // Re-render the scope on every navigation so the missing watcher tracks the
188
- // current entry (consumers re-render through their own version subscription).
189
- const historyVersion = useHistoryVersion(history)
195
+ }, [history, adapter, fireOnChange, historyVersion])
190
196
 
191
- // Mount: canonicalize a non-adapter seed into the adapter, then relay external
192
- // (browser/URL) changes back into the history.
193
197
  useEffect(() => {
194
- const seedLoc = history.current
195
- if (seedLoc !== null && adapter.read() === null) adapter.write(seedLoc, { replace: true })
198
+ const seed = history.current
199
+ if (seed !== null && adapter.read() === null) adapter.write(seed, { replace: true })
196
200
  return adapter.subscribe(() => {
197
- const loc = adapter.read()
198
- if (loc === null) return
201
+ const location = admit(adapter.read())
202
+ if (location === null) return
199
203
  startTransition(() => {
200
- if (history.syncExternal(loc)) fireOnChange()
204
+ if (history.syncExternal(location)) fireOnChange()
201
205
  })
202
206
  })
203
207
  }, [history, adapter, fireOnChange])
204
208
 
205
- // Restore revalidation — once, against the shared memory.
206
- const revalidatedRef = useRef(false)
207
209
  useEffect(() => {
208
- if (revalidatedRef.current || props.restore === undefined) return
209
- revalidatedRef.current = true
210
- void revalidateAndPrune(history, memory)
211
- }, [memory, props.restore, history])
210
+ if (props.restore === undefined) return
211
+ let current = true
212
+ void revalidateAndPrune(history, store, shell).then((changed) => {
213
+ if (!current || !changed) return
214
+ const accepted = history.current
215
+ if (accepted === null) adapter.clear({ replace: true })
216
+ else adapter.write(accepted, { replace: true })
217
+ fireOnChange()
218
+ })
219
+ return () => {
220
+ current = false
221
+ }
222
+ }, [store, shell, history, adapter, props.restore, fireOnChange])
212
223
 
213
- // Missing-anchor watcher: the current entry's live read drives the policy.
214
- const currentShape = history.current?.node ?? null
215
- const currentResult = useMemoryShape(memory, currentShape)
216
- const settled = useSettled(currentResult, currentShape)
217
- const lastPathRef = useRef<string | null>(null)
218
- const handledRef = useRef<string | null>(null)
224
+ const current = history.current
225
+ const result = useGraphPoint(current?.node ?? null)
226
+ const handled = useRef<NodePath | null>(null)
219
227
  useEffect(() => {
220
- const resolved = currentResult?.node ?? null
221
- if (resolved !== null) lastPathRef.current = pathOf(resolved)
222
- }, [currentResult?.node])
223
- useEffect(() => {
224
- const entry = history.current
225
- if (entry === null) return
226
- if ((currentResult?.node ?? null) !== null) {
227
- handledRef.current = null
228
+ if (current === null || result === null || result.pending) return
229
+ if (result.node !== null) {
230
+ handled.current = null
228
231
  return
229
232
  }
230
- if (!settled || handledRef.current === entry.node) return
231
- handledRef.current = entry.node
232
- applyMissingPolicy(entry, missingRef.current ?? 'ancestor', {
233
- lastPath: lastPathRef.current,
234
- result: currentResult,
235
- navigate,
236
- })
237
- // historyVersion re-runs this when the current entry changes.
238
- }, [settled, currentResult, history, navigate, historyVersion])
233
+ if (handled.current === current.node) return
234
+ handled.current = current.node
235
+ const policy = missingRef.current ?? 'hold'
236
+ if (typeof policy === 'function') {
237
+ const target = policy(buildLocation(current, result))
238
+ if (target !== null) navigate(target, { replace: true })
239
+ }
240
+ }, [current, result, navigate, historyVersion])
239
241
 
240
242
  const ctx = useMemo<NavCtx>(
241
- () => ({ history, memory, adapter, navigate }),
242
- [history, memory, adapter, navigate],
243
+ () => ({ history, store, adapter, navigate }),
244
+ [history, store, adapter, navigate],
243
245
  )
244
-
245
246
  return <NavContext.Provider value={ctx}>{props.children}</NavContext.Provider>
246
247
  }
247
248
 
248
- function applyMissingPolicy(
249
- entry: SerializedLocation,
250
- policy: MissingPolicy,
251
- deps: {
252
- lastPath: string | null
253
- result: LiveResult | null
254
- navigate: MutableNavigate
255
- },
256
- ): void {
257
- if (policy === 'hold') return
258
- if (typeof policy === 'function') {
259
- const target = policy(buildLocation(entry, deps.result, true))
260
- if (target !== null) deps.navigate(target, { replace: true })
261
- return
249
+ function admitLocation(location: SerializedLocation): SerializedLocation {
250
+ if (location === null || typeof location !== 'object' || Array.isArray(location)) {
251
+ throw new TypeError('Navigation location is invalid.')
252
+ }
253
+ const node = NodePath(location.node)
254
+ let view: Key.View | undefined
255
+ if (location.view !== undefined) {
256
+ const key = Key(location.view)
257
+ if (!Key.ref(key).startsWith('view.')) throw new TypeError('Navigation View key is invalid.')
258
+ view = key as Key.View
259
+ }
260
+ const params: Record<string, string> = {}
261
+ if (location.params !== undefined) {
262
+ if (
263
+ location.params === null ||
264
+ typeof location.params !== 'object' ||
265
+ Array.isArray(location.params)
266
+ ) {
267
+ throw new TypeError('Navigation params are invalid.')
268
+ }
269
+ for (const [key, value] of Object.entries(location.params)) {
270
+ if (/token|credential|authorization|secret/i.test(key)) {
271
+ throw new TypeError(`Navigation param '${key}' is sensitive.`)
272
+ }
273
+ if (typeof value !== 'string') throw new TypeError(`Navigation param '${key}' is invalid.`)
274
+ params[key] = value
275
+ }
262
276
  }
263
- // 'ancestor': climb one level toward a resolvable ancestor (repeats per settle).
264
- const raw = deps.lastPath ?? pathHint(entry.node)
265
- const parent = raw !== '' ? parentPath(raw) : null
266
- if (parent !== null) deps.navigate(parent, { replace: true })
277
+ return Object.freeze({
278
+ node,
279
+ ...(view === undefined ? {} : { view }),
280
+ ...(Object.keys(params).length === 0 ? {} : { params: Object.freeze(params) }),
281
+ })
267
282
  }
@@ -1,78 +1,97 @@
1
- import type { AnyBoundNode, NodeRefInput } from '@astrale-os/kernel-client'
2
- import type { PathLike } from '@astrale-os/kernel-client/graph'
3
- import type { GraphMemory } from '@astrale-os/kernel-client/store'
1
+ import type { GraphStore } from '@astrale-os/kernel-client/store'
2
+ import type { Node } from '@astrale-os/sdk/graph/node'
3
+ import type { NodePath, PathLike } from '@astrale-os/sdk/graph/path'
4
+ import type { Key } from '@astrale-os/sdk/schema'
5
+ import type { Shell } from '@astrale-os/shell'
4
6
 
5
- /**
6
- * NavSnapshot serialize/restore (API §8.1/§8.2) — the `NavTarget` input space, the
7
- * `serializeTarget` normalization into the id-anchored stored form, and the
8
- * restore-time `revalidateAndPrune` that drops dead entries against the memory.
9
- */
10
- import { normalizeRef } from '@astrale-os/kernel-client'
7
+ import { Path } from '@astrale-os/sdk/graph/path'
8
+ import { Query } from '@astrale-os/sdk/query'
9
+ import { Key as admitKey } from '@astrale-os/sdk/schema'
11
10
 
12
- import type { NavHistory } from './history.js'
13
- import type { SerializedLocation } from './history.js'
14
- import type { Node, NodeId } from './location.js'
11
+ import type { NavHistory, SerializedLocation } from './history.js'
15
12
 
16
- /** Everything `navigate()` / `Link` / `initial` accept (API §8.1). */
13
+ /** Canonical navigation inputs; there is no implicit slash-tree or bare-id grammar. */
17
14
  export type NavTarget =
18
- | NodeId
19
15
  | PathLike
20
16
  | Node
21
- | AnyBoundNode
22
- | { node: NodeId | PathLike; view?: NodeRefInput; params?: Record<string, string> }
17
+ | {
18
+ readonly node: PathLike | Node
19
+ readonly view?: Key.View
20
+ readonly params?: Record<string, string>
21
+ }
22
+
23
+ function isNode(value: PathLike | Node): value is Node {
24
+ return typeof value === 'object' && value !== null && 'id' in value && 'class' in value
25
+ }
26
+
27
+ function nodePath(value: PathLike | Node): NodePath {
28
+ return isNode(value) ? Path.id(value.id).raw : Path.from(value).raw
29
+ }
23
30
 
24
- function isRecord(x: unknown): x is Record<string, unknown> {
25
- return typeof x === 'object' && x !== null
31
+ function isComposite(
32
+ value: NavTarget,
33
+ ): value is Extract<NavTarget, { readonly node: PathLike | Node }> {
34
+ return typeof value === 'object' && value !== null && 'node' in value
26
35
  }
27
36
 
28
- /** Normalize any {@link NavTarget} to its stored, id-anchored serialization. */
37
+ /** Normalize every target to canonical astrale-node-path-v1 text. */
29
38
  export function serializeTarget(target: NavTarget): SerializedLocation {
30
- if (typeof target === 'string') return { node: normalizeRef(target) }
31
- if (isRecord(target) && 'node' in target) {
32
- const t = target as { node: NodeRefInput; view?: NodeRefInput; params?: Record<string, string> }
33
- return {
34
- node: normalizeRef(t.node),
35
- ...(t.view !== undefined ? { view: normalizeRef(t.view) } : {}),
36
- ...(t.params !== undefined ? { params: { ...t.params } } : {}),
37
- }
38
- }
39
- // A Node / BoundNode carries `id` (id-anchored, so rename/move survive) and `path`;
40
- // anything else is a typed `Path` value → its raw address.
41
- if (isRecord(target) && 'id' in target && 'path' in target) {
42
- return { node: normalizeRef((target as { id: NodeRefInput }).id) }
39
+ if (!isComposite(target)) return { node: nodePath(target) }
40
+ return {
41
+ node: nodePath(target.node),
42
+ ...(target.view === undefined ? {} : { view: viewKey(target.view) }),
43
+ ...(target.params === undefined ? {} : { params: { ...target.params } }),
43
44
  }
44
- return { node: normalizeRef(target as NodeRefInput) }
45
45
  }
46
46
 
47
- /**
48
- * Revalidate a restored stack against the memory and drop dead entries (API §8.2).
49
- * Each entry is point-read one-shot; a null/failed read is a dead anchor. The
50
- * cursor re-lands on the surviving current entry, else the nearest survivor
51
- * before it.
52
- */
53
- export async function revalidateAndPrune(history: NavHistory, memory: GraphMemory): Promise<void> {
47
+ function viewKey(input: string): Key.View {
48
+ const key = admitKey(input)
49
+ if (!admitKey.ref(key).startsWith('view.')) throw new TypeError(`Invalid View key '${input}'.`)
50
+ return key as Key.View
51
+ }
52
+
53
+ /** Drop restored entries that no longer resolve under the authenticated Graph capability. */
54
+ export async function revalidateAndPrune(
55
+ history: NavHistory,
56
+ store: GraphStore,
57
+ shell: Shell,
58
+ ): Promise<boolean> {
54
59
  const { entries, cursor } = history.snapshot()
55
- const alive = await Promise.all(entries.map((e) => resolves(memory, e.node)))
56
- if (alive.every(Boolean)) return
60
+ const alive = await Promise.all(entries.map((entry) => resolves(store, shell, entry)))
61
+ if (alive.every(Boolean)) return false
57
62
 
58
63
  const survivors: SerializedLocation[] = []
59
64
  let newCursor = -1
60
65
  let fallback = -1
61
- entries.forEach((entry, i) => {
62
- if (!alive[i]) return
66
+ entries.forEach((entry, index) => {
67
+ if (!alive[index]) return
63
68
  survivors.push(entry)
64
- const idx = survivors.length - 1
65
- if (i === cursor) newCursor = idx
66
- else if (i < cursor) fallback = idx
69
+ const next = survivors.length - 1
70
+ if (index === cursor) newCursor = next
71
+ else if (index < cursor) fallback = next
67
72
  })
68
73
  if (newCursor === -1) newCursor = fallback !== -1 ? fallback : survivors.length > 0 ? 0 : -1
69
74
  history.reset(survivors, newCursor)
75
+ return true
70
76
  }
71
77
 
72
- async function resolves(memory: GraphMemory, ref: string): Promise<boolean> {
78
+ async function resolves(
79
+ store: GraphStore,
80
+ shell: Shell,
81
+ entry: SerializedLocation,
82
+ ): Promise<boolean> {
73
83
  try {
74
- const result = await memory.query(ref, { live: false })
75
- return result.node !== null
84
+ const query = Query.from({ kind: 'node', paths: [entry.node] }).select({ kind: 'graph' })
85
+ const response = await store.read(query, { page: { size: 1 } })
86
+ if (
87
+ response.result.kind !== 'graph' ||
88
+ response.result.selection.kind !== 'node' ||
89
+ response.result.selection.ids.length === 0
90
+ )
91
+ return false
92
+ if (entry.view === undefined) return true
93
+ const routes = await shell.views.resolve(entry.node)
94
+ return routes.some((candidate) => candidate.route.key === entry.view)
76
95
  } catch {
77
96
  return false
78
97
  }