@shipfox/client-shell 0.2.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 (252) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/.turbo/turbo-type$colon$emit.log +1 -0
  3. package/.turbo/turbo-type.log +1 -0
  4. package/CHANGELOG.md +26 -0
  5. package/LICENSE +21 -0
  6. package/dist/components/main-layout.d.ts +11 -0
  7. package/dist/components/main-layout.d.ts.map +1 -0
  8. package/dist/components/main-layout.js +39 -0
  9. package/dist/components/main-layout.js.map +1 -0
  10. package/dist/components/nav-bar.d.ts +4 -0
  11. package/dist/components/nav-bar.d.ts.map +1 -0
  12. package/dist/components/nav-bar.js +62 -0
  13. package/dist/components/nav-bar.js.map +1 -0
  14. package/dist/components/nav-tabs.d.ts +6 -0
  15. package/dist/components/nav-tabs.d.ts.map +1 -0
  16. package/dist/components/nav-tabs.js +38 -0
  17. package/dist/components/nav-tabs.js.map +1 -0
  18. package/dist/components/not-found-page.d.ts +2 -0
  19. package/dist/components/not-found-page.d.ts.map +1 -0
  20. package/dist/components/not-found-page.js +25 -0
  21. package/dist/components/not-found-page.js.map +1 -0
  22. package/dist/components/settings-nav.d.ts +5 -0
  23. package/dist/components/settings-nav.d.ts.map +1 -0
  24. package/dist/components/settings-nav.js +45 -0
  25. package/dist/components/settings-nav.js.map +1 -0
  26. package/dist/components/user-menu.d.ts +2 -0
  27. package/dist/components/user-menu.d.ts.map +1 -0
  28. package/dist/components/user-menu.js +76 -0
  29. package/dist/components/user-menu.js.map +1 -0
  30. package/dist/components/workspace-crumb.d.ts +7 -0
  31. package/dist/components/workspace-crumb.d.ts.map +1 -0
  32. package/dist/components/workspace-crumb.js +54 -0
  33. package/dist/components/workspace-crumb.js.map +1 -0
  34. package/dist/components/workspace-switcher.d.ts +5 -0
  35. package/dist/components/workspace-switcher.d.ts.map +1 -0
  36. package/dist/components/workspace-switcher.js +90 -0
  37. package/dist/components/workspace-switcher.js.map +1 -0
  38. package/dist/compose/compose-routes.d.ts +6 -0
  39. package/dist/compose/compose-routes.d.ts.map +1 -0
  40. package/dist/compose/compose-routes.js +53 -0
  41. package/dist/compose/compose-routes.js.map +1 -0
  42. package/dist/compose/errors.d.ts +26 -0
  43. package/dist/compose/errors.d.ts.map +1 -0
  44. package/dist/compose/errors.js +39 -0
  45. package/dist/compose/errors.js.map +1 -0
  46. package/dist/compose/merge-config.d.ts +4 -0
  47. package/dist/compose/merge-config.d.ts.map +1 -0
  48. package/dist/compose/merge-config.js +28 -0
  49. package/dist/compose/merge-config.js.map +1 -0
  50. package/dist/compose/normalize-route-path.d.ts +2 -0
  51. package/dist/compose/normalize-route-path.d.ts.map +1 -0
  52. package/dist/compose/normalize-route-path.js +7 -0
  53. package/dist/compose/normalize-route-path.js.map +1 -0
  54. package/dist/compose/validate-providers.d.ts +3 -0
  55. package/dist/compose/validate-providers.d.ts.map +1 -0
  56. package/dist/compose/validate-providers.js +32 -0
  57. package/dist/compose/validate-providers.js.map +1 -0
  58. package/dist/compose/validate-registries.d.ts +4 -0
  59. package/dist/compose/validate-registries.d.ts.map +1 -0
  60. package/dist/compose/validate-registries.js +50 -0
  61. package/dist/compose/validate-registries.js.map +1 -0
  62. package/dist/config/shell-config.d.ts +10 -0
  63. package/dist/config/shell-config.d.ts.map +1 -0
  64. package/dist/config/shell-config.js +12 -0
  65. package/dist/config/shell-config.js.map +1 -0
  66. package/dist/contract.d.ts +39 -0
  67. package/dist/contract.d.ts.map +1 -0
  68. package/dist/contract.js +5 -0
  69. package/dist/contract.js.map +1 -0
  70. package/dist/index.d.ts +2 -0
  71. package/dist/index.d.ts.map +1 -0
  72. package/dist/index.js +3 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/runtime/active-workspace.d.ts +4 -0
  75. package/dist/runtime/active-workspace.d.ts.map +1 -0
  76. package/dist/runtime/active-workspace.js +15 -0
  77. package/dist/runtime/active-workspace.js.map +1 -0
  78. package/dist/runtime/anchor-paths.d.ts +9 -0
  79. package/dist/runtime/anchor-paths.d.ts.map +1 -0
  80. package/dist/runtime/anchor-paths.js +18 -0
  81. package/dist/runtime/anchor-paths.js.map +1 -0
  82. package/dist/runtime/anchors.d.ts +19 -0
  83. package/dist/runtime/anchors.d.ts.map +1 -0
  84. package/dist/runtime/anchors.js +125 -0
  85. package/dist/runtime/anchors.js.map +1 -0
  86. package/dist/runtime/assemble-route-tree.d.ts +10 -0
  87. package/dist/runtime/assemble-route-tree.d.ts.map +1 -0
  88. package/dist/runtime/assemble-route-tree.js +30 -0
  89. package/dist/runtime/assemble-route-tree.js.map +1 -0
  90. package/dist/runtime/auth.d.ts +50 -0
  91. package/dist/runtime/auth.d.ts.map +1 -0
  92. package/dist/runtime/auth.js +194 -0
  93. package/dist/runtime/auth.js.map +1 -0
  94. package/dist/runtime/chrome-context.d.ts +10 -0
  95. package/dist/runtime/chrome-context.d.ts.map +1 -0
  96. package/dist/runtime/chrome-context.js +16 -0
  97. package/dist/runtime/chrome-context.js.map +1 -0
  98. package/dist/runtime/compose-client-app.d.ts +13 -0
  99. package/dist/runtime/compose-client-app.d.ts.map +1 -0
  100. package/dist/runtime/compose-client-app.js +93 -0
  101. package/dist/runtime/compose-client-app.js.map +1 -0
  102. package/dist/runtime/define-route.d.ts +19 -0
  103. package/dist/runtime/define-route.d.ts.map +1 -0
  104. package/dist/runtime/define-route.js +12 -0
  105. package/dist/runtime/define-route.js.map +1 -0
  106. package/dist/runtime/index.d.ts +20 -0
  107. package/dist/runtime/index.d.ts.map +1 -0
  108. package/dist/runtime/index.js +21 -0
  109. package/dist/runtime/index.js.map +1 -0
  110. package/dist/runtime/last-workspace.d.ts +4 -0
  111. package/dist/runtime/last-workspace.d.ts.map +1 -0
  112. package/dist/runtime/last-workspace.js +20 -0
  113. package/dist/runtime/last-workspace.js.map +1 -0
  114. package/dist/runtime/nav-order.d.ts +7 -0
  115. package/dist/runtime/nav-order.d.ts.map +1 -0
  116. package/dist/runtime/nav-order.js +8 -0
  117. package/dist/runtime/nav-order.js.map +1 -0
  118. package/dist/runtime/provider-stack.d.ts +14 -0
  119. package/dist/runtime/provider-stack.d.ts.map +1 -0
  120. package/dist/runtime/provider-stack.js +28 -0
  121. package/dist/runtime/provider-stack.js.map +1 -0
  122. package/dist/runtime/registries.d.ts +4 -0
  123. package/dist/runtime/registries.d.ts.map +1 -0
  124. package/dist/runtime/registries.js +23 -0
  125. package/dist/runtime/registries.js.map +1 -0
  126. package/dist/runtime/router-context.d.ts +9 -0
  127. package/dist/runtime/router-context.d.ts.map +1 -0
  128. package/dist/runtime/router-context.js +3 -0
  129. package/dist/runtime/router-context.js.map +1 -0
  130. package/dist/runtime/workspace-setup.d.ts +18 -0
  131. package/dist/runtime/workspace-setup.d.ts.map +1 -0
  132. package/dist/runtime/workspace-setup.js +64 -0
  133. package/dist/runtime/workspace-setup.js.map +1 -0
  134. package/dist/testing/index.d.ts +17 -0
  135. package/dist/testing/index.d.ts.map +1 -0
  136. package/dist/testing/index.js +35 -0
  137. package/dist/testing/index.js.map +1 -0
  138. package/dist/tsconfig.test.tsbuildinfo +1 -0
  139. package/dist/vite/evaluate-features.d.ts +8 -0
  140. package/dist/vite/evaluate-features.d.ts.map +1 -0
  141. package/dist/vite/evaluate-features.js +80 -0
  142. package/dist/vite/evaluate-features.js.map +1 -0
  143. package/dist/vite/generate.d.ts +9 -0
  144. package/dist/vite/generate.d.ts.map +1 -0
  145. package/dist/vite/generate.js +80 -0
  146. package/dist/vite/generate.js.map +1 -0
  147. package/dist/vite/index.d.ts +2 -0
  148. package/dist/vite/index.d.ts.map +1 -0
  149. package/dist/vite/index.js +3 -0
  150. package/dist/vite/index.js.map +1 -0
  151. package/dist/vite/plugin.d.ts +7 -0
  152. package/dist/vite/plugin.d.ts.map +1 -0
  153. package/dist/vite/plugin.js +77 -0
  154. package/dist/vite/plugin.js.map +1 -0
  155. package/package.json +113 -0
  156. package/src/components/main-layout.tsx +45 -0
  157. package/src/components/nav-bar.tsx +41 -0
  158. package/src/components/nav-tabs.test.tsx +60 -0
  159. package/src/components/nav-tabs.tsx +47 -0
  160. package/src/components/not-found-page.tsx +14 -0
  161. package/src/components/settings-nav.tsx +35 -0
  162. package/src/components/user-menu.tsx +59 -0
  163. package/src/components/workspace-crumb.tsx +43 -0
  164. package/src/components/workspace-switcher.tsx +75 -0
  165. package/src/compose/compose-routes.test.ts +112 -0
  166. package/src/compose/compose-routes.ts +60 -0
  167. package/src/compose/errors.ts +54 -0
  168. package/src/compose/merge-config.test.ts +26 -0
  169. package/src/compose/merge-config.ts +27 -0
  170. package/src/compose/normalize-route-path.ts +6 -0
  171. package/src/compose/validate-providers.test.ts +31 -0
  172. package/src/compose/validate-providers.ts +36 -0
  173. package/src/compose/validate-registries.test.ts +92 -0
  174. package/src/compose/validate-registries.ts +61 -0
  175. package/src/config/config-screen.test.tsx +37 -0
  176. package/src/config/shell-config.ts +10 -0
  177. package/src/contract.ts +47 -0
  178. package/src/index.ts +1 -0
  179. package/src/runtime/active-workspace.ts +13 -0
  180. package/src/runtime/anchor-paths.ts +18 -0
  181. package/src/runtime/anchors.tsx +106 -0
  182. package/src/runtime/assemble-route-tree.ts +42 -0
  183. package/src/runtime/auth.tsx +213 -0
  184. package/src/runtime/chrome-context.tsx +22 -0
  185. package/src/runtime/compose-client-app.tsx +122 -0
  186. package/src/runtime/define-route.test.ts +15 -0
  187. package/src/runtime/define-route.ts +33 -0
  188. package/src/runtime/index.ts +19 -0
  189. package/src/runtime/last-workspace.ts +22 -0
  190. package/src/runtime/nav-order.ts +1 -0
  191. package/src/runtime/provider-stack.tsx +39 -0
  192. package/src/runtime/providers.test.tsx +51 -0
  193. package/src/runtime/registries.test.tsx +100 -0
  194. package/src/runtime/registries.ts +42 -0
  195. package/src/runtime/router-context.ts +9 -0
  196. package/src/runtime/routes.test.tsx +48 -0
  197. package/src/runtime/workspace-setup.tsx +61 -0
  198. package/src/testing/index.test.tsx +57 -0
  199. package/src/testing/index.tsx +51 -0
  200. package/src/vite/evaluate-features.test.ts +34 -0
  201. package/src/vite/evaluate-features.ts +93 -0
  202. package/src/vite/generate.test.ts +22 -0
  203. package/src/vite/generate.ts +99 -0
  204. package/src/vite/generated-router.test.tsx +58 -0
  205. package/src/vite/index.ts +1 -0
  206. package/src/vite/plugin.test.ts +181 -0
  207. package/src/vite/plugin.ts +114 -0
  208. package/src/vite/validate.test.ts +166 -0
  209. package/test/default-route-impl.tsx +3 -0
  210. package/test/external/FINDINGS.md +53 -0
  211. package/test/external/README.md +39 -0
  212. package/test/external/fixture/index.html +12 -0
  213. package/test/external/fixture/package.json +32 -0
  214. package/test/external/fixture/src/app.fixture.tsx +72 -0
  215. package/test/external/fixture/src/features/override-impl.tsx +12 -0
  216. package/test/external/fixture/src/features/override.tsx +20 -0
  217. package/test/external/fixture/src/features.collision.ts +15 -0
  218. package/test/external/fixture/src/features.ts +4 -0
  219. package/test/external/fixture/src/link-typecheck.tsx +10 -0
  220. package/test/external/fixture/src/main.tsx +4 -0
  221. package/test/external/fixture/tsconfig.json +15 -0
  222. package/test/external/fixture/vite.config.ts +10 -0
  223. package/test/external/fixture/vitest.config.ts +15 -0
  224. package/test/external/toy-feature/CHANGELOG.md +15 -0
  225. package/test/external/toy-feature/node_modules/.bin/shipfox-biome-check +43 -0
  226. package/test/external/toy-feature/node_modules/.bin/shipfox-biome-format +43 -0
  227. package/test/external/toy-feature/node_modules/.bin/shipfox-biome-lint +43 -0
  228. package/test/external/toy-feature/node_modules/.bin/shipfox-swc +43 -0
  229. package/test/external/toy-feature/node_modules/.bin/shipfox-tsc-check +43 -0
  230. package/test/external/toy-feature/node_modules/.bin/shipfox-tsc-emit +43 -0
  231. package/test/external/toy-feature/package.json +57 -0
  232. package/test/external/toy-feature/src/config.ts +5 -0
  233. package/test/external/toy-feature/src/index.ts +64 -0
  234. package/test/external/toy-feature/src/provider.tsx +33 -0
  235. package/test/external/toy-feature/src/routes/insights.tsx +7 -0
  236. package/test/external/toy-feature/src/routes/settings.tsx +7 -0
  237. package/test/external/toy-feature/tsconfig.build.json +5 -0
  238. package/test/external/toy-feature/tsconfig.json +1 -0
  239. package/test/external/verify.mjs +318 -0
  240. package/test/fixtures/features.ts +56 -0
  241. package/test/named-route-impl.tsx +3 -0
  242. package/test/not-route-impl.ts +1 -0
  243. package/test/render.tsx +67 -0
  244. package/test/search-route-impl.tsx +7 -0
  245. package/test/setup.ts +3 -0
  246. package/test/typecheck/shipfox-app.gen.ts +85 -0
  247. package/test/typecheck/types.tsx +18 -0
  248. package/tsconfig.build.json +10 -0
  249. package/tsconfig.build.tsbuildinfo +1 -0
  250. package/tsconfig.json +1 -0
  251. package/tsconfig.test.json +12 -0
  252. package/vitest.config.ts +22 -0
@@ -0,0 +1,18 @@
1
+ const anchorPaths = {
2
+ root: '/',
3
+ workspaceLayout: '/workspaces/$wid',
4
+ projectLayout: '/workspaces/$wid/projects/$pid',
5
+ workspaceSettings: '/workspaces/$wid/settings'
6
+ };
7
+ export { anchorPaths };
8
+ export function routePathForAnchor(anchor, fullPath) {
9
+ const anchorPath = anchorPaths[anchor];
10
+ if (anchor === 'root') return fullPath;
11
+ if (fullPath === anchorPath) return '/';
12
+ if (!fullPath.startsWith(`${anchorPath}/`)) {
13
+ throw new Error(`Route "${fullPath}" must be nested under anchor "${anchor}" (${anchorPath}).`);
14
+ }
15
+ return fullPath.slice(anchorPath.length);
16
+ }
17
+
18
+ //# sourceMappingURL=anchor-paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/runtime/anchor-paths.ts"],"sourcesContent":["const anchorPaths = {\n root: '/',\n workspaceLayout: '/workspaces/$wid',\n projectLayout: '/workspaces/$wid/projects/$pid',\n workspaceSettings: '/workspaces/$wid/settings',\n} as const;\n\nexport {anchorPaths};\n\nexport function routePathForAnchor(anchor: keyof typeof anchorPaths, fullPath: string): string {\n const anchorPath = anchorPaths[anchor];\n if (anchor === 'root') return fullPath;\n if (fullPath === anchorPath) return '/';\n if (!fullPath.startsWith(`${anchorPath}/`)) {\n throw new Error(`Route \"${fullPath}\" must be nested under anchor \"${anchor}\" (${anchorPath}).`);\n }\n return fullPath.slice(anchorPath.length);\n}\n"],"names":["anchorPaths","root","workspaceLayout","projectLayout","workspaceSettings","routePathForAnchor","anchor","fullPath","anchorPath","startsWith","Error","slice","length"],"mappings":"AAAA,MAAMA,cAAc;IAClBC,MAAM;IACNC,iBAAiB;IACjBC,eAAe;IACfC,mBAAmB;AACrB;AAEA,SAAQJ,WAAW,GAAE;AAErB,OAAO,SAASK,mBAAmBC,MAAgC,EAAEC,QAAgB;IACnF,MAAMC,aAAaR,WAAW,CAACM,OAAO;IACtC,IAAIA,WAAW,QAAQ,OAAOC;IAC9B,IAAIA,aAAaC,YAAY,OAAO;IACpC,IAAI,CAACD,SAASE,UAAU,CAAC,GAAGD,WAAW,CAAC,CAAC,GAAG;QAC1C,MAAM,IAAIE,MAAM,CAAC,OAAO,EAAEH,SAAS,+BAA+B,EAAED,OAAO,GAAG,EAAEE,WAAW,EAAE,CAAC;IAChG;IACA,OAAOD,SAASI,KAAK,CAACH,WAAWI,MAAM;AACzC"}
@@ -0,0 +1,19 @@
1
+ import type { NavTabEntry, SettingsSectionEntry } from '#contract.js';
2
+ import type { RouterContext } from './router-context.js';
3
+ export { routePathForAnchor } from './anchor-paths.js';
4
+ export declare function buildAnchorSkeleton({ navigation, settingsSections, }: {
5
+ navigation: readonly NavTabEntry[];
6
+ settingsSections: readonly SettingsSectionEntry[];
7
+ }): {
8
+ anchors: {
9
+ root: import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>;
10
+ workspaceLayout: import("@tanstack/react-router").Route<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/workspaces/$wid", "/workspaces/$wid", string, "/workspaces/$wid", undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, ({ context, params, location }: import("@tanstack/router-core").BeforeLoadContextOptions<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, "/workspaces/$wid", unknown, undefined>) => Promise<import("./workspace-setup.js").WorkspaceSetupState | undefined>, {}, undefined, unknown, unknown, unknown, unknown, undefined>;
11
+ projectLayout: import("@tanstack/react-router").Route<unknown, import("@tanstack/react-router").Route<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/workspaces/$wid", "/workspaces/$wid", string, "/workspaces/$wid", undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, ({ context, params, location }: import("@tanstack/router-core").BeforeLoadContextOptions<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, "/workspaces/$wid", unknown, undefined>) => Promise<import("./workspace-setup.js").WorkspaceSetupState | undefined>, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/projects/$pid", "/workspaces/$wid/projects/$pid", string, "/workspaces/$wid/projects/$pid", undefined, import("@tanstack/router-core").ResolveParams<"/projects/$pid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>;
12
+ workspaceSettings: import("@tanstack/react-router").Route<unknown, import("@tanstack/react-router").Route<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/workspaces/$wid", "/workspaces/$wid", string, "/workspaces/$wid", undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, ({ context, params, location }: import("@tanstack/router-core").BeforeLoadContextOptions<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, "/workspaces/$wid", unknown, undefined>) => Promise<import("./workspace-setup.js").WorkspaceSetupState | undefined>, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/settings", "/workspaces/$wid/settings", string, "/workspaces/$wid/settings", undefined, import("@tanstack/router-core").ResolveParams<"/settings">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>;
13
+ };
14
+ rootRoute: import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>;
15
+ workspaceLayout: import("@tanstack/react-router").Route<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/workspaces/$wid", "/workspaces/$wid", string, "/workspaces/$wid", undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, ({ context, params, location }: import("@tanstack/router-core").BeforeLoadContextOptions<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, "/workspaces/$wid", unknown, undefined>) => Promise<import("./workspace-setup.js").WorkspaceSetupState | undefined>, {}, undefined, unknown, unknown, unknown, unknown, undefined>;
16
+ projectLayout: import("@tanstack/react-router").Route<unknown, import("@tanstack/react-router").Route<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/workspaces/$wid", "/workspaces/$wid", string, "/workspaces/$wid", undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, ({ context, params, location }: import("@tanstack/router-core").BeforeLoadContextOptions<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, "/workspaces/$wid", unknown, undefined>) => Promise<import("./workspace-setup.js").WorkspaceSetupState | undefined>, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/projects/$pid", "/workspaces/$wid/projects/$pid", string, "/workspaces/$wid/projects/$pid", undefined, import("@tanstack/router-core").ResolveParams<"/projects/$pid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>;
17
+ workspaceSettings: import("@tanstack/react-router").Route<unknown, import("@tanstack/react-router").Route<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/workspaces/$wid", "/workspaces/$wid", string, "/workspaces/$wid", undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, ({ context, params, location }: import("@tanstack/router-core").BeforeLoadContextOptions<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, "/workspaces/$wid", unknown, undefined>) => Promise<import("./workspace-setup.js").WorkspaceSetupState | undefined>, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/settings", "/workspaces/$wid/settings", string, "/workspaces/$wid/settings", undefined, import("@tanstack/router-core").ResolveParams<"/settings">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>;
18
+ };
19
+ //# sourceMappingURL=anchors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anchors.d.ts","sourceRoot":"","sources":["../../src/runtime/anchors.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,WAAW,EAAE,oBAAoB,EAAC,MAAM,cAAc,CAAC;AAKpE,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAGvD,OAAO,EAAC,kBAAkB,EAAC,MAAM,mBAAmB,CAAC;AAErD,wBAAgB,mBAAmB,CAAC,EAClC,UAAU,EACV,gBAAgB,GACjB,EAAE;IACD,UAAU,EAAE,SAAS,WAAW,EAAE,CAAC;IACnC,gBAAgB,EAAE,SAAS,oBAAoB,EAAE,CAAC;CACnD;;;;;;;;;;;EAmFA"}
@@ -0,0 +1,125 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ShipfoxLoader } from '@shipfox/react-ui/loader';
3
+ import { Header, Text } from '@shipfox/react-ui/typography';
4
+ import { createRootRouteWithContext, createRoute, Outlet, redirect } from '@tanstack/react-router';
5
+ import { MainLayout } from '#components/main-layout.js';
6
+ import { NotFoundPage } from '#components/not-found-page.js';
7
+ import { SettingsNav } from '#components/settings-nav.js';
8
+ import { useActiveWorkspace } from './active-workspace.js';
9
+ import { anchorPaths } from './anchor-paths.js';
10
+ import { useChrome } from './chrome-context.js';
11
+ import { rememberLastWorkspaceId } from './last-workspace.js';
12
+ import { WorkspaceLayoutErrorRoute, WorkspaceSetupPending } from './workspace-setup.js';
13
+ export { routePathForAnchor } from './anchor-paths.js';
14
+ export function buildAnchorSkeleton({ navigation, settingsSections }) {
15
+ const rootRoute = createRootRouteWithContext()({
16
+ component: Outlet,
17
+ notFoundComponent: NotFoundPage
18
+ });
19
+ const workspaceLayout = createRoute({
20
+ getParentRoute: ()=>rootRoute,
21
+ path: anchorPaths.workspaceLayout,
22
+ beforeLoad: async ({ context, params, location })=>{
23
+ try {
24
+ rememberLastWorkspaceId(params.wid);
25
+ } catch {
26
+ // Local storage is best effort.
27
+ }
28
+ const auth = context.auth;
29
+ if (!auth || auth.isLoading || !context.queryClient) return;
30
+ if (!auth.isAuthenticated) throw redirect({
31
+ to: '/auth/login',
32
+ search: {
33
+ redirect: location.href
34
+ }
35
+ });
36
+ if (!auth.workspaces.some((workspace)=>workspace.id === params.wid)) throw redirect({
37
+ to: '/'
38
+ });
39
+ if (!context.workspaceSetup) throw new Error('Client composition includes workspace routes but no workspaceSetup gate.');
40
+ return await context.workspaceSetup({
41
+ queryClient: context.queryClient,
42
+ workspaceId: params.wid,
43
+ pathname: location.pathname
44
+ });
45
+ },
46
+ pendingComponent: ()=>/*#__PURE__*/ _jsx("div", {
47
+ className: "flex h-screen items-center justify-center",
48
+ children: /*#__PURE__*/ _jsx(ShipfoxLoader, {
49
+ size: 64,
50
+ animation: "circular",
51
+ color: "orange",
52
+ background: "dark"
53
+ })
54
+ }),
55
+ errorComponent: WorkspaceLayoutErrorRoute,
56
+ component: ()=>{
57
+ const setupState = workspaceLayout.useRouteContext();
58
+ return setupState.hideProjectNavigation === undefined ? /*#__PURE__*/ _jsx(WorkspaceSetupPending, {}) : /*#__PURE__*/ _jsx(MainLayout, {
59
+ navigation: navigation,
60
+ hideProjectNavigation: setupState.hideProjectNavigation
61
+ });
62
+ }
63
+ });
64
+ const projectLayout = createRoute({
65
+ getParentRoute: ()=>workspaceLayout,
66
+ path: '/projects/$pid',
67
+ component: ()=>{
68
+ const { ProjectLayoutGuard } = useChrome();
69
+ return /*#__PURE__*/ _jsx(ProjectLayoutGuard, {});
70
+ }
71
+ });
72
+ const workspaceSettings = createRoute({
73
+ getParentRoute: ()=>workspaceLayout,
74
+ path: '/settings',
75
+ component: ()=>{
76
+ const workspace = useActiveWorkspace();
77
+ return /*#__PURE__*/ _jsxs("div", {
78
+ className: "flex w-full flex-col gap-24",
79
+ children: [
80
+ /*#__PURE__*/ _jsxs("header", {
81
+ className: "flex flex-col gap-6",
82
+ children: [
83
+ /*#__PURE__*/ _jsx(Header, {
84
+ variant: "h2",
85
+ children: "Workspace settings"
86
+ }),
87
+ /*#__PURE__*/ _jsxs(Text, {
88
+ size: "sm",
89
+ className: "text-foreground-neutral-muted",
90
+ children: [
91
+ "Configure ",
92
+ workspace.name,
93
+ "."
94
+ ]
95
+ })
96
+ ]
97
+ }),
98
+ /*#__PURE__*/ _jsxs("div", {
99
+ className: "grid grid-cols-[180px_minmax(0,1fr)] gap-32 max-[760px]:grid-cols-1",
100
+ children: [
101
+ /*#__PURE__*/ _jsx(SettingsNav, {
102
+ entries: settingsSections
103
+ }),
104
+ /*#__PURE__*/ _jsx(Outlet, {})
105
+ ]
106
+ })
107
+ ]
108
+ });
109
+ }
110
+ });
111
+ return {
112
+ anchors: {
113
+ root: rootRoute,
114
+ workspaceLayout,
115
+ projectLayout,
116
+ workspaceSettings
117
+ },
118
+ rootRoute,
119
+ workspaceLayout,
120
+ projectLayout,
121
+ workspaceSettings
122
+ };
123
+ }
124
+
125
+ //# sourceMappingURL=anchors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/runtime/anchors.tsx"],"sourcesContent":["import {ShipfoxLoader} from '@shipfox/react-ui/loader';\nimport {Header, Text} from '@shipfox/react-ui/typography';\nimport {createRootRouteWithContext, createRoute, Outlet, redirect} from '@tanstack/react-router';\nimport {MainLayout} from '#components/main-layout.js';\nimport {NotFoundPage} from '#components/not-found-page.js';\nimport {SettingsNav} from '#components/settings-nav.js';\nimport type {NavTabEntry, SettingsSectionEntry} from '#contract.js';\nimport {useActiveWorkspace} from './active-workspace.js';\nimport {anchorPaths} from './anchor-paths.js';\nimport {useChrome} from './chrome-context.js';\nimport {rememberLastWorkspaceId} from './last-workspace.js';\nimport type {RouterContext} from './router-context.js';\nimport {WorkspaceLayoutErrorRoute, WorkspaceSetupPending} from './workspace-setup.js';\n\nexport {routePathForAnchor} from './anchor-paths.js';\n\nexport function buildAnchorSkeleton({\n navigation,\n settingsSections,\n}: {\n navigation: readonly NavTabEntry[];\n settingsSections: readonly SettingsSectionEntry[];\n}) {\n const rootRoute = createRootRouteWithContext<RouterContext>()({\n component: Outlet,\n notFoundComponent: NotFoundPage,\n });\n const workspaceLayout = createRoute({\n getParentRoute: () => rootRoute,\n path: anchorPaths.workspaceLayout,\n beforeLoad: async ({context, params, location}) => {\n try {\n rememberLastWorkspaceId(params.wid);\n } catch {\n // Local storage is best effort.\n }\n const auth = context.auth;\n if (!auth || auth.isLoading || !context.queryClient) return;\n if (!auth.isAuthenticated)\n throw redirect({to: '/auth/login' as never, search: {redirect: location.href} as never});\n if (!auth.workspaces.some((workspace) => workspace.id === params.wid))\n throw redirect({to: '/'});\n if (!context.workspaceSetup)\n throw new Error('Client composition includes workspace routes but no workspaceSetup gate.');\n return await context.workspaceSetup({\n queryClient: context.queryClient,\n workspaceId: params.wid,\n pathname: location.pathname,\n });\n },\n pendingComponent: () => (\n <div className=\"flex h-screen items-center justify-center\">\n <ShipfoxLoader size={64} animation=\"circular\" color=\"orange\" background=\"dark\" />\n </div>\n ),\n errorComponent: WorkspaceLayoutErrorRoute,\n component: () => {\n const setupState = workspaceLayout.useRouteContext() as {hideProjectNavigation?: boolean};\n return setupState.hideProjectNavigation === undefined ? (\n <WorkspaceSetupPending />\n ) : (\n <MainLayout\n navigation={navigation}\n hideProjectNavigation={setupState.hideProjectNavigation}\n />\n );\n },\n });\n const projectLayout = createRoute({\n getParentRoute: () => workspaceLayout,\n path: '/projects/$pid',\n component: () => {\n const {ProjectLayoutGuard} = useChrome();\n return <ProjectLayoutGuard />;\n },\n });\n const workspaceSettings = createRoute({\n getParentRoute: () => workspaceLayout,\n path: '/settings',\n component: () => {\n const workspace = useActiveWorkspace();\n return (\n <div className=\"flex w-full flex-col gap-24\">\n <header className=\"flex flex-col gap-6\">\n <Header variant=\"h2\">Workspace settings</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Configure {workspace.name}.\n </Text>\n </header>\n\n <div className=\"grid grid-cols-[180px_minmax(0,1fr)] gap-32 max-[760px]:grid-cols-1\">\n <SettingsNav entries={settingsSections} />\n <Outlet />\n </div>\n </div>\n );\n },\n });\n return {\n anchors: {root: rootRoute, workspaceLayout, projectLayout, workspaceSettings},\n rootRoute,\n workspaceLayout,\n projectLayout,\n workspaceSettings,\n };\n}\n"],"names":["ShipfoxLoader","Header","Text","createRootRouteWithContext","createRoute","Outlet","redirect","MainLayout","NotFoundPage","SettingsNav","useActiveWorkspace","anchorPaths","useChrome","rememberLastWorkspaceId","WorkspaceLayoutErrorRoute","WorkspaceSetupPending","routePathForAnchor","buildAnchorSkeleton","navigation","settingsSections","rootRoute","component","notFoundComponent","workspaceLayout","getParentRoute","path","beforeLoad","context","params","location","wid","auth","isLoading","queryClient","isAuthenticated","to","search","href","workspaces","some","workspace","id","workspaceSetup","Error","workspaceId","pathname","pendingComponent","div","className","size","animation","color","background","errorComponent","setupState","useRouteContext","hideProjectNavigation","undefined","projectLayout","ProjectLayoutGuard","workspaceSettings","header","variant","name","entries","anchors","root"],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { ComposedRoute } from '#compose/compose-routes.js';
2
+ import { buildAnchorSkeleton } from '#runtime/anchors.js';
3
+ import type { RouteImpl } from '#runtime/define-route.js';
4
+ export type ResolveRouteImpl = (specifier: string) => RouteImpl | Promise<RouteImpl>;
5
+ export declare function assembleRouteTree(routes: readonly ComposedRoute[], options: {
6
+ resolveImpl: ResolveRouteImpl;
7
+ navigation: Parameters<typeof buildAnchorSkeleton>[0]['navigation'];
8
+ settingsSections: Parameters<typeof buildAnchorSkeleton>[0]['settingsSections'];
9
+ }): Promise<import("@tanstack/router-core").Route<import("@tanstack/router-core").Register, any, "/", "/", string, "__root__", undefined, {}, import("./router-context").RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, readonly [...import("@tanstack/react-router").Route<unknown, import("@tanstack/router-core").AnyRoute, "/", "/" | `/${any}/`, string, "/" | `/${any}/`, undefined, import("@tanstack/router-core").ResolveParams<"/">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>[], import("@tanstack/router-core").Route<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, import("./router-context").RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/workspaces/$wid", "/workspaces/$wid", string, "/workspaces/$wid", undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, ({ context, params, location }: import("@tanstack/router-core").BeforeLoadContextOptions<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, import("./router-context").RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, "/workspaces/$wid", unknown, undefined>) => Promise<import("./workspace-setup").WorkspaceSetupState | undefined>, {}, undefined, readonly [...import("@tanstack/react-router").Route<unknown, import("@tanstack/router-core").AnyRoute, "/", "/" | `/${any}/`, string, "/" | `/${any}/`, undefined, import("@tanstack/router-core").ResolveParams<"/">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>[], import("@tanstack/router-core").Route<unknown, import("@tanstack/react-router").Route<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, import("./router-context").RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/workspaces/$wid", "/workspaces/$wid", string, "/workspaces/$wid", undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, ({ context, params, location }: import("@tanstack/router-core").BeforeLoadContextOptions<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, import("./router-context").RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, "/workspaces/$wid", unknown, undefined>) => Promise<import("./workspace-setup").WorkspaceSetupState | undefined>, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/projects/$pid", "/workspaces/$wid/projects/$pid", string, "/workspaces/$wid/projects/$pid", undefined, import("@tanstack/router-core").ResolveParams<"/projects/$pid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, import("@tanstack/react-router").Route<unknown, import("@tanstack/router-core").AnyRoute, "/", "/" | `/${any}/`, string, "/" | `/${any}/`, undefined, import("@tanstack/router-core").ResolveParams<"/">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>[], unknown, unknown, unknown, undefined>, import("@tanstack/router-core").Route<unknown, import("@tanstack/react-router").Route<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, import("./router-context").RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/workspaces/$wid", "/workspaces/$wid", string, "/workspaces/$wid", undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, ({ context, params, location }: import("@tanstack/router-core").BeforeLoadContextOptions<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, import("./router-context").RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, "/workspaces/$wid", unknown, undefined>) => Promise<import("./workspace-setup").WorkspaceSetupState | undefined>, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/settings", "/workspaces/$wid/settings", string, "/workspaces/$wid/settings", undefined, import("@tanstack/router-core").ResolveParams<"/settings">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, import("@tanstack/react-router").Route<unknown, import("@tanstack/router-core").AnyRoute, "/", "/" | `/${any}/`, string, "/" | `/${any}/`, undefined, import("@tanstack/router-core").ResolveParams<"/">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>[], unknown, unknown, unknown, undefined>], unknown, unknown, unknown, undefined>], unknown, unknown, unknown, undefined>>;
10
+ //# sourceMappingURL=assemble-route-tree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assemble-route-tree.d.ts","sourceRoot":"","sources":["../../src/runtime/assemble-route-tree.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,4BAA4B,CAAC;AAC9D,OAAO,EAAC,mBAAmB,EAAqB,MAAM,qBAAqB,CAAC;AAC5E,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAExD,MAAM,MAAM,gBAAgB,GAAG,CAAC,SAAS,EAAE,MAAM,KAAK,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;AAErF,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,SAAS,aAAa,EAAE,EAChC,OAAO,EAAE;IACP,WAAW,EAAE,gBAAgB,CAAC;IAC9B,UAAU,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;IACpE,gBAAgB,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC;CACjF,o+MA4BF"}
@@ -0,0 +1,30 @@
1
+ import { createRoute } from '@tanstack/react-router';
2
+ import { buildAnchorSkeleton, routePathForAnchor } from '#runtime/anchors.js';
3
+ export async function assembleRouteTree(routes, options) {
4
+ const skeleton = buildAnchorSkeleton(options);
5
+ const children = await Promise.all(routes.map(async (route)=>{
6
+ const impl = await options.resolveImpl(route.impl);
7
+ return {
8
+ parent: route.parent,
9
+ route: createRoute({
10
+ getParentRoute: ()=>skeleton.anchors[route.parent],
11
+ path: routePathForAnchor(route.parent, route.path),
12
+ ...impl.options
13
+ })
14
+ };
15
+ }));
16
+ const childrenFor = (anchor)=>children.filter((child)=>child.parent === anchor).map((child)=>child.route);
17
+ const projectLayout = skeleton.projectLayout.addChildren(childrenFor('projectLayout'));
18
+ const workspaceSettings = skeleton.workspaceSettings.addChildren(childrenFor('workspaceSettings'));
19
+ const workspaceLayout = skeleton.workspaceLayout.addChildren([
20
+ ...childrenFor('workspaceLayout'),
21
+ projectLayout,
22
+ workspaceSettings
23
+ ]);
24
+ return skeleton.rootRoute.addChildren([
25
+ ...childrenFor('root'),
26
+ workspaceLayout
27
+ ]);
28
+ }
29
+
30
+ //# sourceMappingURL=assemble-route-tree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/runtime/assemble-route-tree.ts"],"sourcesContent":["import {createRoute} from '@tanstack/react-router';\nimport type {ComposedRoute} from '#compose/compose-routes.js';\nimport {buildAnchorSkeleton, routePathForAnchor} from '#runtime/anchors.js';\nimport type {RouteImpl} from '#runtime/define-route.js';\n\nexport type ResolveRouteImpl = (specifier: string) => RouteImpl | Promise<RouteImpl>;\n\nexport async function assembleRouteTree(\n routes: readonly ComposedRoute[],\n options: {\n resolveImpl: ResolveRouteImpl;\n navigation: Parameters<typeof buildAnchorSkeleton>[0]['navigation'];\n settingsSections: Parameters<typeof buildAnchorSkeleton>[0]['settingsSections'];\n },\n) {\n const skeleton = buildAnchorSkeleton(options);\n const children = await Promise.all(\n routes.map(async (route) => {\n const impl = await options.resolveImpl(route.impl);\n return {\n parent: route.parent,\n route: createRoute({\n getParentRoute: () => skeleton.anchors[route.parent] as never,\n path: routePathForAnchor(route.parent, route.path),\n ...impl.options,\n } as never),\n };\n }),\n );\n const childrenFor = (anchor: keyof typeof skeleton.anchors) =>\n children.filter((child) => child.parent === anchor).map((child) => child.route);\n const projectLayout = skeleton.projectLayout.addChildren(childrenFor('projectLayout'));\n const workspaceSettings = skeleton.workspaceSettings.addChildren(\n childrenFor('workspaceSettings'),\n );\n const workspaceLayout = skeleton.workspaceLayout.addChildren([\n ...childrenFor('workspaceLayout'),\n projectLayout,\n workspaceSettings,\n ]);\n return skeleton.rootRoute.addChildren([...childrenFor('root'), workspaceLayout]);\n}\n"],"names":["createRoute","buildAnchorSkeleton","routePathForAnchor","assembleRouteTree","routes","options","skeleton","children","Promise","all","map","route","impl","resolveImpl","parent","getParentRoute","anchors","path","childrenFor","anchor","filter","child","projectLayout","addChildren","workspaceSettings","workspaceLayout","rootRoute"],"mappings":"AAAA,SAAQA,WAAW,QAAO,yBAAyB;AAEnD,SAAQC,mBAAmB,EAAEC,kBAAkB,QAAO,sBAAsB;AAK5E,OAAO,eAAeC,kBACpBC,MAAgC,EAChCC,OAIC;IAED,MAAMC,WAAWL,oBAAoBI;IACrC,MAAME,WAAW,MAAMC,QAAQC,GAAG,CAChCL,OAAOM,GAAG,CAAC,OAAOC;QAChB,MAAMC,OAAO,MAAMP,QAAQQ,WAAW,CAACF,MAAMC,IAAI;QACjD,OAAO;YACLE,QAAQH,MAAMG,MAAM;YACpBH,OAAOX,YAAY;gBACjBe,gBAAgB,IAAMT,SAASU,OAAO,CAACL,MAAMG,MAAM,CAAC;gBACpDG,MAAMf,mBAAmBS,MAAMG,MAAM,EAAEH,MAAMM,IAAI;gBACjD,GAAGL,KAAKP,OAAO;YACjB;QACF;IACF;IAEF,MAAMa,cAAc,CAACC,SACnBZ,SAASa,MAAM,CAAC,CAACC,QAAUA,MAAMP,MAAM,KAAKK,QAAQT,GAAG,CAAC,CAACW,QAAUA,MAAMV,KAAK;IAChF,MAAMW,gBAAgBhB,SAASgB,aAAa,CAACC,WAAW,CAACL,YAAY;IACrE,MAAMM,oBAAoBlB,SAASkB,iBAAiB,CAACD,WAAW,CAC9DL,YAAY;IAEd,MAAMO,kBAAkBnB,SAASmB,eAAe,CAACF,WAAW,CAAC;WACxDL,YAAY;QACfI;QACAE;KACD;IACD,OAAOlB,SAASoB,SAAS,CAACH,WAAW,CAAC;WAAIL,YAAY;QAASO;KAAgB;AACjF"}
@@ -0,0 +1,50 @@
1
+ import type { LoginResponseDto, UserDto } from '@shipfox/api-auth-dto';
2
+ import type { MembershipWithWorkspaceDto } from '@shipfox/api-workspaces-dto';
3
+ import type { PropsWithChildren } from 'react';
4
+ export type AuthStatus = 'loading' | 'authenticated' | 'guest';
5
+ export interface Workspace {
6
+ id: string;
7
+ name: string;
8
+ membershipId: string;
9
+ }
10
+ export interface AuthState {
11
+ status: AuthStatus;
12
+ token?: string;
13
+ user?: UserDto;
14
+ workspaces?: Workspace[];
15
+ }
16
+ export interface AuthStateValue extends AuthState {
17
+ isLoading: boolean;
18
+ isAuthenticated: boolean;
19
+ workspaces: Workspace[];
20
+ hasWorkspace: boolean;
21
+ }
22
+ export declare const initialAuthState: AuthState;
23
+ export declare const authStateAtom: import("jotai").PrimitiveAtom<AuthState> & {
24
+ init: AuthState;
25
+ };
26
+ export declare const authRefreshQueryKey: readonly ["auth", "refresh"];
27
+ export declare const userWorkspacesQueryKey: readonly ["workspaces", "mine"];
28
+ export declare function toAuthenticatedState(result: LoginResponseDto, memberships?: MembershipWithWorkspaceDto[]): AuthState;
29
+ export declare function useAuthState(): AuthStateValue;
30
+ export declare function listUserWorkspaces(token?: string): Promise<{
31
+ memberships: MembershipWithWorkspaceDto[];
32
+ }>;
33
+ export declare function getAuthRefreshDelayMs(token: string, nowMs?: number): number | undefined;
34
+ export declare function useRefreshAuth(): () => Promise<{
35
+ token: string;
36
+ user: {
37
+ id: string;
38
+ email: string;
39
+ name: string | null;
40
+ email_verified_at: string | null;
41
+ status: "active" | "suspended" | "deleted";
42
+ created_at: string;
43
+ updated_at: string;
44
+ };
45
+ }>;
46
+ export interface AuthRuntimeProps extends PropsWithChildren {
47
+ effects?: boolean;
48
+ }
49
+ export declare function AuthRuntime({ children, effects }: AuthRuntimeProps): import("react").ReactNode;
50
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/runtime/auth.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,gBAAgB,EAAE,OAAO,EAAC,MAAM,uBAAuB,CAAC;AACrE,OAAO,KAAK,EAAC,0BAA0B,EAAC,MAAM,6BAA6B,CAAC;AAI5E,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,OAAO,CAAC;AAO7C,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,eAAe,GAAG,OAAO,CAAC;AAE/D,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,cAAe,SAAQ,SAAS;IAC/C,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;IACzB,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,eAAO,MAAM,gBAAgB,EAAE,SAA+B,CAAC;AAC/D,eAAO,MAAM,aAAa;;CAAoC,CAAC;AAC/D,eAAO,MAAM,mBAAmB,8BAA+B,CAAC;AAChE,eAAO,MAAM,sBAAsB,iCAAkC,CAAC;AAEtE,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,gBAAgB,EACxB,WAAW,GAAE,0BAA0B,EAAO,GAC7C,SAAS,CAWX;AAED,wBAAgB,YAAY,IAAI,cAAc,CAY7C;AAED,wBAAsB,kBAAkB,CAAC,KAAK,CAAC,EAAE,MAAM;iBACf,0BAA0B,EAAE;GAInE;AAwBD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,SAAa,GAAG,MAAM,GAAG,SAAS,CAG3F;AAED,wBAAgB,cAAc;;;;;;;;;;;GA8B7B;AAED,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACzD,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,WAAW,CAAC,EAAC,QAAQ,EAAE,OAAc,EAAC,EAAE,gBAAgB,6BAyEvE"}
@@ -0,0 +1,194 @@
1
+ import { ApiError, apiRequest, configureApiClient } from '@shipfox/client-api';
2
+ import { useQueryClient } from '@tanstack/react-query';
3
+ import { atom, useAtomValue, useSetAtom, useStore } from 'jotai';
4
+ import { useCallback, useEffect, useMemo } from 'react';
5
+ const REFRESH_EARLY_MS = 5 * 60 * 1000;
6
+ const REFRESH_RETRY_DELAY_MS = 60_000;
7
+ const BASE64_URL_REPLACEMENTS = {
8
+ dash: /-/g,
9
+ underscore: /_/g
10
+ };
11
+ export const initialAuthState = {
12
+ status: 'loading'
13
+ };
14
+ export const authStateAtom = atom(initialAuthState);
15
+ export const authRefreshQueryKey = [
16
+ 'auth',
17
+ 'refresh'
18
+ ];
19
+ export const userWorkspacesQueryKey = [
20
+ 'workspaces',
21
+ 'mine'
22
+ ];
23
+ export function toAuthenticatedState(result, memberships = []) {
24
+ return {
25
+ status: 'authenticated',
26
+ token: result.token,
27
+ user: result.user,
28
+ workspaces: memberships.map((membership)=>({
29
+ id: membership.workspace_id,
30
+ name: membership.workspace_name,
31
+ membershipId: membership.id
32
+ }))
33
+ };
34
+ }
35
+ export function useAuthState() {
36
+ const state = useAtomValue(authStateAtom);
37
+ return useMemo(()=>({
38
+ ...state,
39
+ workspaces: state.workspaces ?? [],
40
+ isLoading: state.status === 'loading',
41
+ isAuthenticated: state.status === 'authenticated',
42
+ hasWorkspace: (state.workspaces ?? []).length > 0
43
+ }), [
44
+ state
45
+ ]);
46
+ }
47
+ export async function listUserWorkspaces(token) {
48
+ return await apiRequest('/workspaces', token ? {
49
+ headers: {
50
+ authorization: `Bearer ${token}`
51
+ }
52
+ } : {});
53
+ }
54
+ async function refreshAuthRequest() {
55
+ return await apiRequest('/auth/refresh', {
56
+ method: 'POST'
57
+ });
58
+ }
59
+ function decodeBase64Url(value) {
60
+ const base64 = value.replace(BASE64_URL_REPLACEMENTS.dash, '+').replace(BASE64_URL_REPLACEMENTS.underscore, '/');
61
+ return atob(base64.padEnd(Math.ceil(base64.length / 4) * 4, '='));
62
+ }
63
+ function readJwtExp(token) {
64
+ const [, payload] = token.split('.');
65
+ if (!payload) return undefined;
66
+ try {
67
+ const parsed = JSON.parse(decodeBase64Url(payload));
68
+ return typeof parsed.exp === 'number' && Number.isFinite(parsed.exp) ? parsed.exp : undefined;
69
+ } catch {
70
+ return undefined;
71
+ }
72
+ }
73
+ export function getAuthRefreshDelayMs(token, nowMs = Date.now()) {
74
+ const exp = readJwtExp(token);
75
+ return exp === undefined ? undefined : exp * 1000 - nowMs - REFRESH_EARLY_MS;
76
+ }
77
+ export function useRefreshAuth() {
78
+ const queryClient = useQueryClient();
79
+ const setState = useSetAtom(authStateAtom);
80
+ return useCallback(async ()=>{
81
+ try {
82
+ const result = await queryClient.fetchQuery({
83
+ queryKey: authRefreshQueryKey,
84
+ queryFn: refreshAuthRequest,
85
+ retry: false,
86
+ staleTime: 0
87
+ });
88
+ let memberships = [];
89
+ try {
90
+ const workspaces = await queryClient.fetchQuery({
91
+ queryKey: userWorkspacesQueryKey,
92
+ queryFn: ()=>listUserWorkspaces(result.token),
93
+ retry: false,
94
+ staleTime: 0
95
+ });
96
+ memberships = workspaces.memberships;
97
+ } catch {
98
+ // A valid session remains usable while workspace hydration retries on the next route load.
99
+ }
100
+ setState(toAuthenticatedState(result, memberships));
101
+ return result;
102
+ } catch (error) {
103
+ if (error instanceof ApiError && error.status === 401) setState({
104
+ status: 'guest'
105
+ });
106
+ throw error;
107
+ }
108
+ }, [
109
+ queryClient,
110
+ setState
111
+ ]);
112
+ }
113
+ export function AuthRuntime({ children, effects = true }) {
114
+ const store = useStore();
115
+ const authState = useAtomValue(authStateAtom);
116
+ const refreshAuth = useRefreshAuth();
117
+ useEffect(()=>{
118
+ if (!effects) return;
119
+ configureApiClient({
120
+ getAccessToken: ()=>store.get(authStateAtom).token,
121
+ refreshAccessToken: async ()=>(await refreshAuth()).token
122
+ });
123
+ }, [
124
+ effects,
125
+ refreshAuth,
126
+ store
127
+ ]);
128
+ useEffect(()=>{
129
+ if (!effects) return;
130
+ refreshAuth().catch(()=>undefined);
131
+ }, [
132
+ effects,
133
+ refreshAuth
134
+ ]);
135
+ useEffect(()=>{
136
+ if (!effects || authState.status !== 'authenticated' || !authState.token) return;
137
+ let timeout;
138
+ let disposed = false;
139
+ let refreshing = false;
140
+ const clearRefreshTimer = ()=>{
141
+ if (timeout !== undefined) clearTimeout(timeout);
142
+ timeout = undefined;
143
+ };
144
+ const scheduleRefresh = (delayMs)=>{
145
+ clearRefreshTimer();
146
+ timeout = setTimeout(runRefresh, Math.max(0, delayMs));
147
+ };
148
+ const retryIfStillDue = ()=>{
149
+ const current = store.get(authStateAtom);
150
+ if (current.status !== 'authenticated' || !current.token) return;
151
+ const delay = getAuthRefreshDelayMs(current.token);
152
+ if (delay !== undefined && delay <= 0) scheduleRefresh(REFRESH_RETRY_DELAY_MS);
153
+ };
154
+ function runRefresh() {
155
+ if (disposed || refreshing) return;
156
+ refreshing = true;
157
+ clearRefreshTimer();
158
+ refreshAuth().catch(()=>undefined).finally(()=>{
159
+ refreshing = false;
160
+ if (!disposed) retryIfStillDue();
161
+ });
162
+ }
163
+ const refreshIfDue = ()=>{
164
+ const current = store.get(authStateAtom);
165
+ if (current.status !== 'authenticated' || !current.token) return;
166
+ const delay = getAuthRefreshDelayMs(current.token);
167
+ if (delay !== undefined && delay <= 0) runRefresh();
168
+ };
169
+ const refreshIfVisible = ()=>{
170
+ if (document.visibilityState === 'visible') refreshIfDue();
171
+ };
172
+ const delay = getAuthRefreshDelayMs(authState.token);
173
+ if (delay !== undefined) scheduleRefresh(delay);
174
+ window.addEventListener('focus', refreshIfDue);
175
+ window.addEventListener('online', refreshIfDue);
176
+ document.addEventListener('visibilitychange', refreshIfVisible);
177
+ return ()=>{
178
+ disposed = true;
179
+ clearRefreshTimer();
180
+ window.removeEventListener('focus', refreshIfDue);
181
+ window.removeEventListener('online', refreshIfDue);
182
+ document.removeEventListener('visibilitychange', refreshIfVisible);
183
+ };
184
+ }, [
185
+ authState.status,
186
+ authState.token,
187
+ effects,
188
+ refreshAuth,
189
+ store
190
+ ]);
191
+ return children;
192
+ }
193
+
194
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/runtime/auth.tsx"],"sourcesContent":["import type {LoginResponseDto, UserDto} from '@shipfox/api-auth-dto';\nimport type {MembershipWithWorkspaceDto} from '@shipfox/api-workspaces-dto';\nimport {ApiError, apiRequest, configureApiClient} from '@shipfox/client-api';\nimport {useQueryClient} from '@tanstack/react-query';\nimport {atom, useAtomValue, useSetAtom, useStore} from 'jotai';\nimport type {PropsWithChildren} from 'react';\nimport {useCallback, useEffect, useMemo} from 'react';\n\nconst REFRESH_EARLY_MS = 5 * 60 * 1000;\nconst REFRESH_RETRY_DELAY_MS = 60_000;\nconst BASE64_URL_REPLACEMENTS = {dash: /-/g, underscore: /_/g} as const;\n\nexport type AuthStatus = 'loading' | 'authenticated' | 'guest';\n\nexport interface Workspace {\n id: string;\n name: string;\n membershipId: string;\n}\n\nexport interface AuthState {\n status: AuthStatus;\n token?: string;\n user?: UserDto;\n workspaces?: Workspace[];\n}\n\nexport interface AuthStateValue extends AuthState {\n isLoading: boolean;\n isAuthenticated: boolean;\n workspaces: Workspace[];\n hasWorkspace: boolean;\n}\n\nexport const initialAuthState: AuthState = {status: 'loading'};\nexport const authStateAtom = atom<AuthState>(initialAuthState);\nexport const authRefreshQueryKey = ['auth', 'refresh'] as const;\nexport const userWorkspacesQueryKey = ['workspaces', 'mine'] as const;\n\nexport function toAuthenticatedState(\n result: LoginResponseDto,\n memberships: MembershipWithWorkspaceDto[] = [],\n): AuthState {\n return {\n status: 'authenticated',\n token: result.token,\n user: result.user,\n workspaces: memberships.map((membership) => ({\n id: membership.workspace_id,\n name: membership.workspace_name,\n membershipId: membership.id,\n })),\n };\n}\n\nexport function useAuthState(): AuthStateValue {\n const state = useAtomValue(authStateAtom);\n return useMemo(\n () => ({\n ...state,\n workspaces: state.workspaces ?? [],\n isLoading: state.status === 'loading',\n isAuthenticated: state.status === 'authenticated',\n hasWorkspace: (state.workspaces ?? []).length > 0,\n }),\n [state],\n );\n}\n\nexport async function listUserWorkspaces(token?: string) {\n return await apiRequest<{memberships: MembershipWithWorkspaceDto[]}>(\n '/workspaces',\n token ? {headers: {authorization: `Bearer ${token}`}} : {},\n );\n}\n\nasync function refreshAuthRequest() {\n return await apiRequest<LoginResponseDto>('/auth/refresh', {method: 'POST'});\n}\n\nfunction decodeBase64Url(value: string): string {\n const base64 = value\n .replace(BASE64_URL_REPLACEMENTS.dash, '+')\n .replace(BASE64_URL_REPLACEMENTS.underscore, '/');\n return atob(base64.padEnd(Math.ceil(base64.length / 4) * 4, '='));\n}\n\nfunction readJwtExp(token: string): number | undefined {\n const [, payload] = token.split('.');\n if (!payload) return undefined;\n try {\n const parsed = JSON.parse(decodeBase64Url(payload)) as {exp?: unknown};\n return typeof parsed.exp === 'number' && Number.isFinite(parsed.exp) ? parsed.exp : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport function getAuthRefreshDelayMs(token: string, nowMs = Date.now()): number | undefined {\n const exp = readJwtExp(token);\n return exp === undefined ? undefined : exp * 1000 - nowMs - REFRESH_EARLY_MS;\n}\n\nexport function useRefreshAuth() {\n const queryClient = useQueryClient();\n const setState = useSetAtom(authStateAtom);\n return useCallback(async () => {\n try {\n const result = await queryClient.fetchQuery({\n queryKey: authRefreshQueryKey,\n queryFn: refreshAuthRequest,\n retry: false,\n staleTime: 0,\n });\n let memberships: MembershipWithWorkspaceDto[] = [];\n try {\n const workspaces = await queryClient.fetchQuery({\n queryKey: userWorkspacesQueryKey,\n queryFn: () => listUserWorkspaces(result.token),\n retry: false,\n staleTime: 0,\n });\n memberships = workspaces.memberships;\n } catch {\n // A valid session remains usable while workspace hydration retries on the next route load.\n }\n setState(toAuthenticatedState(result, memberships));\n return result;\n } catch (error) {\n if (error instanceof ApiError && error.status === 401) setState({status: 'guest'});\n throw error;\n }\n }, [queryClient, setState]);\n}\n\nexport interface AuthRuntimeProps extends PropsWithChildren {\n effects?: boolean;\n}\n\nexport function AuthRuntime({children, effects = true}: AuthRuntimeProps) {\n const store = useStore();\n const authState = useAtomValue(authStateAtom);\n const refreshAuth = useRefreshAuth();\n\n useEffect(() => {\n if (!effects) return;\n configureApiClient({\n getAccessToken: () => store.get(authStateAtom).token,\n refreshAccessToken: async () => (await refreshAuth()).token,\n });\n }, [effects, refreshAuth, store]);\n\n useEffect(() => {\n if (!effects) return;\n refreshAuth().catch(() => undefined);\n }, [effects, refreshAuth]);\n\n useEffect(() => {\n if (!effects || authState.status !== 'authenticated' || !authState.token) return;\n\n let timeout: ReturnType<typeof setTimeout> | undefined;\n let disposed = false;\n let refreshing = false;\n const clearRefreshTimer = () => {\n if (timeout !== undefined) clearTimeout(timeout);\n timeout = undefined;\n };\n const scheduleRefresh = (delayMs: number) => {\n clearRefreshTimer();\n timeout = setTimeout(runRefresh, Math.max(0, delayMs));\n };\n const retryIfStillDue = () => {\n const current = store.get(authStateAtom);\n if (current.status !== 'authenticated' || !current.token) return;\n const delay = getAuthRefreshDelayMs(current.token);\n if (delay !== undefined && delay <= 0) scheduleRefresh(REFRESH_RETRY_DELAY_MS);\n };\n function runRefresh() {\n if (disposed || refreshing) return;\n refreshing = true;\n clearRefreshTimer();\n refreshAuth()\n .catch(() => undefined)\n .finally(() => {\n refreshing = false;\n if (!disposed) retryIfStillDue();\n });\n }\n const refreshIfDue = () => {\n const current = store.get(authStateAtom);\n if (current.status !== 'authenticated' || !current.token) return;\n const delay = getAuthRefreshDelayMs(current.token);\n if (delay !== undefined && delay <= 0) runRefresh();\n };\n const refreshIfVisible = () => {\n if (document.visibilityState === 'visible') refreshIfDue();\n };\n const delay = getAuthRefreshDelayMs(authState.token);\n if (delay !== undefined) scheduleRefresh(delay);\n window.addEventListener('focus', refreshIfDue);\n window.addEventListener('online', refreshIfDue);\n document.addEventListener('visibilitychange', refreshIfVisible);\n return () => {\n disposed = true;\n clearRefreshTimer();\n window.removeEventListener('focus', refreshIfDue);\n window.removeEventListener('online', refreshIfDue);\n document.removeEventListener('visibilitychange', refreshIfVisible);\n };\n }, [authState.status, authState.token, effects, refreshAuth, store]);\n\n return children;\n}\n"],"names":["ApiError","apiRequest","configureApiClient","useQueryClient","atom","useAtomValue","useSetAtom","useStore","useCallback","useEffect","useMemo","REFRESH_EARLY_MS","REFRESH_RETRY_DELAY_MS","BASE64_URL_REPLACEMENTS","dash","underscore","initialAuthState","status","authStateAtom","authRefreshQueryKey","userWorkspacesQueryKey","toAuthenticatedState","result","memberships","token","user","workspaces","map","membership","id","workspace_id","name","workspace_name","membershipId","useAuthState","state","isLoading","isAuthenticated","hasWorkspace","length","listUserWorkspaces","headers","authorization","refreshAuthRequest","method","decodeBase64Url","value","base64","replace","atob","padEnd","Math","ceil","readJwtExp","payload","split","undefined","parsed","JSON","parse","exp","Number","isFinite","getAuthRefreshDelayMs","nowMs","Date","now","useRefreshAuth","queryClient","setState","fetchQuery","queryKey","queryFn","retry","staleTime","error","AuthRuntime","children","effects","store","authState","refreshAuth","getAccessToken","get","refreshAccessToken","catch","timeout","disposed","refreshing","clearRefreshTimer","clearTimeout","scheduleRefresh","delayMs","setTimeout","runRefresh","max","retryIfStillDue","current","delay","finally","refreshIfDue","refreshIfVisible","document","visibilityState","window","addEventListener","removeEventListener"],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { ComponentType, PropsWithChildren } from 'react';
2
+ export interface ChromeSlots {
3
+ ProjectBreadcrumb: ComponentType;
4
+ ProjectLayoutGuard: ComponentType;
5
+ }
6
+ export declare function ChromeProvider({ chrome, children, }: PropsWithChildren<{
7
+ chrome: ChromeSlots | undefined;
8
+ }>): import("react").JSX.Element;
9
+ export declare function useChrome(): ChromeSlots;
10
+ //# sourceMappingURL=chrome-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chrome-context.d.ts","sourceRoot":"","sources":["../../src/runtime/chrome-context.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,aAAa,EAAE,iBAAiB,EAAC,MAAM,OAAO,CAAC;AAG5D,MAAM,WAAW,WAAW;IAC1B,iBAAiB,EAAE,aAAa,CAAC;IACjC,kBAAkB,EAAE,aAAa,CAAC;CACnC;AAID,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,QAAQ,GACT,EAAE,iBAAiB,CAAC;IAAC,MAAM,EAAE,WAAW,GAAG,SAAS,CAAA;CAAC,CAAC,+BAEtD;AAED,wBAAgB,SAAS,IAAI,WAAW,CAIvC"}
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext } from 'react';
3
+ const ChromeContext = /*#__PURE__*/ createContext(undefined);
4
+ export function ChromeProvider({ chrome, children }) {
5
+ return /*#__PURE__*/ _jsx(ChromeContext.Provider, {
6
+ value: chrome,
7
+ children: children
8
+ });
9
+ }
10
+ export function useChrome() {
11
+ const chrome = useContext(ChromeContext);
12
+ if (!chrome) throw new Error('Client composition must provide browser chrome slots.');
13
+ return chrome;
14
+ }
15
+
16
+ //# sourceMappingURL=chrome-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/runtime/chrome-context.tsx"],"sourcesContent":["import type {ComponentType, PropsWithChildren} from 'react';\nimport {createContext, useContext} from 'react';\n\nexport interface ChromeSlots {\n ProjectBreadcrumb: ComponentType;\n ProjectLayoutGuard: ComponentType;\n}\n\nconst ChromeContext = createContext<ChromeSlots | undefined>(undefined);\n\nexport function ChromeProvider({\n chrome,\n children,\n}: PropsWithChildren<{chrome: ChromeSlots | undefined}>) {\n return <ChromeContext.Provider value={chrome}>{children}</ChromeContext.Provider>;\n}\n\nexport function useChrome(): ChromeSlots {\n const chrome = useContext(ChromeContext);\n if (!chrome) throw new Error('Client composition must provide browser chrome slots.');\n return chrome;\n}\n"],"names":["createContext","useContext","ChromeContext","undefined","ChromeProvider","chrome","children","Provider","value","useChrome","Error"],"mappings":";AACA,SAAQA,aAAa,EAAEC,UAAU,QAAO,QAAQ;AAOhD,MAAMC,8BAAgBF,cAAuCG;AAE7D,OAAO,SAASC,eAAe,EAC7BC,MAAM,EACNC,QAAQ,EAC6C;IACrD,qBAAO,KAACJ,cAAcK,QAAQ;QAACC,OAAOH;kBAASC;;AACjD;AAEA,OAAO,SAASG;IACd,MAAMJ,SAASJ,WAAWC;IAC1B,IAAI,CAACG,QAAQ,MAAM,IAAIK,MAAM;IAC7B,OAAOL;AACT"}
@@ -0,0 +1,13 @@
1
+ import { type AnyRouter } from '@tanstack/react-router';
2
+ import type { ClientFeature } from '#contract.js';
3
+ import { type ChromeSlots } from './chrome-context.js';
4
+ import type { WorkspaceSetupGate } from './workspace-setup.js';
5
+ export declare function composeClientApp({ features, router, chrome, workspaceSetup, }: {
6
+ features: readonly ClientFeature[];
7
+ router: AnyRouter;
8
+ chrome?: ChromeSlots;
9
+ workspaceSetup?: WorkspaceSetupGate;
10
+ }): {
11
+ mount(element: HTMLElement): void;
12
+ };
13
+ //# sourceMappingURL=compose-client-app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose-client-app.d.ts","sourceRoot":"","sources":["../../src/runtime/compose-client-app.tsx"],"names":[],"mappings":"AAWA,OAAO,EAAC,KAAK,SAAS,EAAiB,MAAM,wBAAwB,CAAC;AAQtE,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,cAAc,CAAC;AAEhD,OAAO,EAAiB,KAAK,WAAW,EAAC,MAAM,qBAAqB,CAAC;AAErE,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,sBAAsB,CAAC;AAE7D,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,MAAM,EACN,MAAM,EACN,cAAc,GACf,EAAE;IACD,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAC;IACnC,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,cAAc,CAAC,EAAE,kBAAkB,CAAC;CACrC;mBAkBkB,WAAW,GAAG,IAAI;EAiCpC"}