@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,122 @@
1
+ import type { LiveResult } from '@astrale-os/kernel-client/store'
2
+
3
+ import { rawOf } from '@astrale-os/kernel-client/graph'
4
+ /**
5
+ * NavLocation projection (API §8.1) — the live, id-anchored location a scope
6
+ * points at (live path, missing flag), its `buildLocation` assembly, the
7
+ * `useSettled` guard against a one-render false-empty, and the framework-free
8
+ * path math (parent/chain) the scope and hooks share.
9
+ */
10
+ import { useEffect, useRef } from 'react'
11
+
12
+ import type { SerializedLocation } from './history'
13
+
14
+ /**
15
+ * The rich graph `Node`, derived from the memory's read envelope — kernel-client
16
+ * does not re-export `Node` at its root yet (API §14 G8), so shell-react reads it
17
+ * off `LiveResult` rather than taking a direct kernel-core dependency.
18
+ */
19
+ export type Node = NonNullable<LiveResult['node']>
20
+ export type NodeId = Node['id']
21
+
22
+ /** The live, id-anchored location a scope currently points at (API §8.1). */
23
+ export interface NavLocation {
24
+ /** The anchor — survives rename/move. */
25
+ readonly id: NodeId
26
+ /** LIVE projection of the node's current path (empty until first resolve). */
27
+ readonly path: string
28
+ /** Live memory node — null while cold, or after the anchor stops resolving. */
29
+ readonly node: Node | null
30
+ /** View function ref rendering it, when one is open. */
31
+ readonly view?: string
32
+ readonly params: Readonly<Record<string, string>>
33
+ /** The anchor no longer resolves (deleted / masked). */
34
+ readonly missing: boolean
35
+ }
36
+
37
+ const EMPTY_PARAMS: Readonly<Record<string, string>> = Object.freeze({})
38
+
39
+ /** The idle location a scope with no current entry projects (inert / boot). */
40
+ export const IDLE_LOCATION: NavLocation = {
41
+ id: '' as NodeId,
42
+ path: '',
43
+ node: null,
44
+ params: EMPTY_PARAMS,
45
+ missing: false,
46
+ }
47
+
48
+ /** Project a serialized entry + its live read into the public {@link NavLocation}. */
49
+ export function buildLocation(
50
+ entry: SerializedLocation | null,
51
+ result: LiveResult | null,
52
+ settled: boolean,
53
+ ): NavLocation {
54
+ if (entry === null) return IDLE_LOCATION
55
+ const node = result?.node ?? null
56
+ return {
57
+ id: (node?.id ?? idHint(entry.node)) as NodeId,
58
+ path: node ? pathOf(node) : pathHint(entry.node),
59
+ node,
60
+ ...(entry.view !== undefined ? { view: entry.view } : {}),
61
+ params: entry.params ?? IDLE_LOCATION.params,
62
+ missing: settled && node === null,
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Whether a read has definitively settled — data present, a hard error, or a
68
+ * completed empty fetch. Guards `missing` (and the missing policy) against the
69
+ * one-render false-empty before a cold shape's first fetch is even in flight.
70
+ */
71
+ export function useSettled(result: LiveResult | null, key: string | null): boolean {
72
+ const sawPending = useRef(new Map<string, boolean>())
73
+ useEffect(() => {
74
+ if (key !== null && result?.pending === true) sawPending.current.set(key, true)
75
+ }, [key, result?.pending])
76
+ if (key === null || result === null) return false
77
+ if (result.node !== null) return true
78
+ if (result.error !== undefined) return true
79
+ return !result.pending && sawPending.current.get(key) === true
80
+ }
81
+
82
+ // ── path math ──────────────────────────────────────────────────────────────
83
+
84
+ /** The raw path of a live node (empty when cold). */
85
+ export function pathOf(node: Node | null): string {
86
+ return node ? rawOf(node.path) : ''
87
+ }
88
+
89
+ /** Best-effort anchor id from a stored ref (`@<id>` → id, else the raw ref). */
90
+ export function idHint(ref: string): string {
91
+ return ref.startsWith('@') ? ref.slice(1) : ref
92
+ }
93
+
94
+ /** Best-effort path from a stored ref (a `/…` ref is already a path). */
95
+ export function pathHint(ref: string): string {
96
+ return ref.startsWith('/') ? ref : ''
97
+ }
98
+
99
+ /** The parent absolute path, or null at (or above) root. */
100
+ export function parentPath(raw: string): string | null {
101
+ const norm = raw.replace(/\/+$/, '')
102
+ if (norm === '' || norm === '/') return null
103
+ const idx = norm.lastIndexOf('/')
104
+ return idx <= 0 ? '/' : norm.slice(0, idx)
105
+ }
106
+
107
+ /**
108
+ * The inclusive, root-first path chain for an absolute path — the ancestry the
109
+ * kernel tree encodes in the slug path (`/a/b/c` → `/`, `/a`, `/a/b`, `/a/b/c`).
110
+ * The node itself is the last element, so breadcrumbs map the chain directly.
111
+ */
112
+ export function chainPaths(raw: string): string[] {
113
+ const norm = raw.replace(/\/+$/, '')
114
+ if (norm === '' || norm === '/') return ['/']
115
+ const out = ['/']
116
+ let acc = ''
117
+ for (const seg of norm.split('/').filter(Boolean)) {
118
+ acc += `/${seg}`
119
+ out.push(acc)
120
+ }
121
+ return out
122
+ }
@@ -0,0 +1,267 @@
1
+ import type { GraphMemory, LiveResult, ShapeInput } from '@astrale-os/kernel-client/store'
2
+ import type { ReactElement, ReactNode } from 'react'
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
+ */
12
+ import {
13
+ createContext,
14
+ startTransition,
15
+ useCallback,
16
+ useContext,
17
+ useEffect,
18
+ useMemo,
19
+ useRef,
20
+ useSyncExternalStore,
21
+ } from 'react'
22
+
23
+ import type { NavAdapter } from './adapters/adapter'
24
+ import type { NavSnapshot, SerializedLocation } from './history'
25
+ import type { NavLocation } from './location'
26
+ import type { NavTarget } from './snapshot'
27
+
28
+ import { useMemory } from '../graph/memory/scope'
29
+ import { memoryAdapter } from './adapters/memory.adapter'
30
+ import { NavHistory, seedEntries } from './history'
31
+ import { buildLocation, parentPath, pathHint, pathOf, useSettled } from './location'
32
+ import { revalidateAndPrune, serializeTarget } from './snapshot'
33
+
34
+ /** The imperative navigation surface (API §8.2). */
35
+ export interface Navigate {
36
+ (to: NavTarget, opts?: { replace?: boolean }): void
37
+ back(): void
38
+ forward(): void
39
+ /** To the parent via `has_parent`; no-op at root or while the node is cold. */
40
+ up(): void
41
+ readonly canBack: boolean
42
+ readonly canForward: boolean
43
+ }
44
+
45
+ /** A mutable `Navigate` — the scope stamps `canBack`/`canForward` onto it live. */
46
+ export interface MutableNavigate {
47
+ (to: Parameters<Navigate>[0], opts?: Parameters<Navigate>[1]): void
48
+ back(): void
49
+ forward(): void
50
+ up(): void
51
+ canBack: boolean
52
+ canForward: boolean
53
+ }
54
+
55
+ /** The nearest scope's shared handle (stable for the scope's lifetime). */
56
+ export interface NavCtx {
57
+ readonly history: NavHistory
58
+ readonly memory: GraphMemory
59
+ readonly adapter: NavAdapter
60
+ readonly navigate: MutableNavigate
61
+ }
62
+
63
+ export const NavContext = createContext<NavCtx | null>(null)
64
+ NavContext.displayName = 'AstraleNavContext'
65
+
66
+ /** The nearest scope, or throw — the door every public nav hook goes through. */
67
+ export function useNavCtx(): NavCtx {
68
+ const ctx = useContext(NavContext)
69
+ if (ctx === null) throw new Error('nav hooks must be used within a <NavScope>')
70
+ return ctx
71
+ }
72
+
73
+ /** The nearest scope or null (for `useAncestors`, usable outside a scope). */
74
+ export function useOptionalNavCtx(): NavCtx | null {
75
+ return useContext(NavContext)
76
+ }
77
+
78
+ const NOOP_UNSUB = (): void => undefined
79
+
80
+ /** Re-render on any history change (no-op subscription when there is no scope). */
81
+ export function useHistoryVersion(history: NavHistory | null): number {
82
+ const subscribe = useCallback(
83
+ (cb: () => void) => (history !== null ? history.subscribe(cb) : NOOP_UNSUB),
84
+ [history],
85
+ )
86
+ const getSnapshot = useCallback(() => (history !== null ? history.getVersion() : 0), [history])
87
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot)
88
+ }
89
+
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],
98
+ )
99
+ const getSnapshot = useCallback(
100
+ () => (shape !== null ? memory.getSnapshot(shape) : null),
101
+ [memory, shape],
102
+ )
103
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot)
104
+ }
105
+
106
+ /** The missing-anchor policy — a fallback strategy or a caller-supplied resolver. */
107
+ export type MissingPolicy = 'ancestor' | 'hold' | ((loc: NavLocation) => NavTarget | null)
108
+
109
+ export interface NavScopeProps {
110
+ /** Persistence seam. Default: a fresh {@link memoryAdapter} (captured once). */
111
+ adapter?: NavAdapter
112
+ /** A saved snapshot — revalidated against the memory; dead entries dropped. */
113
+ restore?: NavSnapshot
114
+ /** Seed target when neither `adapter.read()` nor `restore` yields one. */
115
+ 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'`. */
119
+ missing?: MissingPolicy
120
+ children: ReactNode
121
+ }
122
+
123
+ 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.
128
+ const adapterRef = useRef<NavAdapter | null>(null)
129
+ if (adapterRef.current === null) adapterRef.current = props.adapter ?? memoryAdapter()
130
+ const adapter = adapterRef.current
131
+
132
+ const historyRef = useRef<NavHistory | null>(null)
133
+ if (historyRef.current === null) {
134
+ const seed = seedEntries(
135
+ adapter.read(),
136
+ props.restore,
137
+ props.initial !== undefined ? serializeTarget(props.initial) : null,
138
+ )
139
+ historyRef.current = new NavHistory(seed.entries, seed.cursor)
140
+ }
141
+ 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
+ const onChangeRef = useRef(props.onChange)
147
+ onChangeRef.current = props.onChange
148
+ const missingRef = useRef(props.missing)
149
+ missingRef.current = props.missing
150
+
151
+ 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())
157
+ }, [history])
158
+
159
+ // The one stable navigate object; hooks re-stamp canBack/canForward onto it.
160
+ const navigate = useMemo<MutableNavigate>(() => {
161
+ const commit = (changed: boolean, replace: boolean): void => {
162
+ if (!changed) return
163
+ const loc = history.current
164
+ if (loc !== null) adapter.write(loc, { replace })
165
+ fireOnChange()
166
+ }
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))
171
+ }) as MutableNavigate
172
+ nav.back = () => startTransition(() => commit(history.back(), false))
173
+ 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)
181
+ }
182
+ nav.canBack = history.canBack
183
+ nav.canForward = history.canForward
184
+ 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)
190
+
191
+ // Mount: canonicalize a non-adapter seed into the adapter, then relay external
192
+ // (browser/URL) changes back into the history.
193
+ useEffect(() => {
194
+ const seedLoc = history.current
195
+ if (seedLoc !== null && adapter.read() === null) adapter.write(seedLoc, { replace: true })
196
+ return adapter.subscribe(() => {
197
+ const loc = adapter.read()
198
+ if (loc === null) return
199
+ startTransition(() => {
200
+ if (history.syncExternal(loc)) fireOnChange()
201
+ })
202
+ })
203
+ }, [history, adapter, fireOnChange])
204
+
205
+ // Restore revalidation — once, against the shared memory.
206
+ const revalidatedRef = useRef(false)
207
+ useEffect(() => {
208
+ if (revalidatedRef.current || props.restore === undefined) return
209
+ revalidatedRef.current = true
210
+ void revalidateAndPrune(history, memory)
211
+ }, [memory, props.restore, history])
212
+
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)
219
+ 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
+ return
229
+ }
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])
239
+
240
+ const ctx = useMemo<NavCtx>(
241
+ () => ({ history, memory, adapter, navigate }),
242
+ [history, memory, adapter, navigate],
243
+ )
244
+
245
+ return <NavContext.Provider value={ctx}>{props.children}</NavContext.Provider>
246
+ }
247
+
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
262
+ }
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 })
267
+ }
@@ -0,0 +1,79 @@
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'
4
+
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'
11
+
12
+ import type { NavHistory } from './history'
13
+ import type { SerializedLocation } from './history'
14
+ import type { Node, NodeId } from './location'
15
+
16
+ /** Everything `navigate()` / `Link` / `initial` accept (API §8.1). */
17
+ export type NavTarget =
18
+ | NodeId
19
+ | PathLike
20
+ | Node
21
+ | AnyBoundNode
22
+ | { node: NodeId | PathLike; view?: NodeRefInput; params?: Record<string, string> }
23
+
24
+ function isRecord(x: unknown): x is Record<string, unknown> {
25
+ return typeof x === 'object' && x !== null
26
+ }
27
+
28
+ /** Normalize any {@link NavTarget} to its stored, id-anchored serialization. */
29
+ 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) }
43
+ }
44
+ return { node: normalizeRef(target as NodeRefInput) }
45
+ }
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> {
54
+ const { entries, cursor } = history.snapshot()
55
+ const alive = await Promise.all(entries.map((e) => resolves(memory, e.node)))
56
+ if (alive.every(Boolean)) return
57
+
58
+ const survivors: SerializedLocation[] = []
59
+ let newCursor = -1
60
+ let fallback = -1
61
+ entries.forEach((entry, i) => {
62
+ if (!alive[i]) return
63
+ survivors.push(entry)
64
+ const idx = survivors.length - 1
65
+ if (i === cursor) newCursor = idx
66
+ else if (i < cursor) fallback = idx
67
+ })
68
+ if (newCursor === -1) newCursor = fallback !== -1 ? fallback : survivors.length > 0 ? 0 : -1
69
+ history.reset(survivors, newCursor)
70
+ }
71
+
72
+ async function resolves(memory: GraphMemory, ref: string): Promise<boolean> {
73
+ try {
74
+ const result = await memory.query(ref, { live: false })
75
+ return result.node !== null
76
+ } catch {
77
+ return false
78
+ }
79
+ }