@byline/host-tanstack-start 3.20.4 → 4.0.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 (140) hide show
  1. package/dist/admin-shell/admin-activity/list.js +15 -5
  2. package/dist/admin-shell/admin-roles/delete.js +2 -1
  3. package/dist/admin-shell/admin-roles/list.js +2 -1
  4. package/dist/admin-shell/admin-users/delete.js +2 -1
  5. package/dist/admin-shell/admin-users/list.js +4 -7
  6. package/dist/admin-shell/chrome/admin-app-bar.js +2 -1
  7. package/dist/admin-shell/chrome/admin-menu-drawer.js +9 -8
  8. package/dist/admin-shell/chrome/admin-menu-provider.js +2 -1
  9. package/dist/admin-shell/chrome/dashboard.js +4 -3
  10. package/dist/admin-shell/chrome/sign-in-page.d.ts +2 -2
  11. package/dist/admin-shell/chrome/sign-in-page.js +2 -2
  12. package/dist/admin-shell/collections/create.js +4 -3
  13. package/dist/admin-shell/collections/document-history.js +6 -1
  14. package/dist/admin-shell/collections/edit.js +18 -14
  15. package/dist/admin-shell/collections/history.js +5 -4
  16. package/dist/admin-shell/collections/list.js +8 -7
  17. package/dist/admin-shell/collections/restore-version-modal.js +2 -1
  18. package/dist/admin-shell/collections/tree-list.js +53 -17
  19. package/dist/admin-shell/collections/view-menu.js +9 -8
  20. package/dist/auth/auth-context.d.ts +13 -0
  21. package/dist/auth/auth-context.js +4 -0
  22. package/dist/auth/request-scope.d.ts +16 -0
  23. package/dist/auth/request-scope.js +24 -0
  24. package/dist/integrations/abilities.js +8 -7
  25. package/dist/integrations/byline-public-client.d.ts +38 -0
  26. package/dist/integrations/byline-public-client.js +8 -4
  27. package/dist/integrations/byline-viewer-client.d.ts +6 -0
  28. package/dist/integrations/byline-viewer-client.js +22 -17
  29. package/dist/integrations/start-errors.js +2 -1
  30. package/dist/routes/admin-path.d.ts +22 -0
  31. package/dist/routes/admin-path.js +35 -0
  32. package/dist/routes/create-admin-account-route.js +3 -2
  33. package/dist/routes/create-admin-activity-route.js +3 -2
  34. package/dist/routes/create-admin-dashboard-route.js +2 -1
  35. package/dist/routes/create-admin-layout-route.d.ts +1 -1
  36. package/dist/routes/create-admin-layout-route.js +3 -1
  37. package/dist/routes/create-admin-permissions-route.js +3 -2
  38. package/dist/routes/create-admin-role-edit-route.js +4 -3
  39. package/dist/routes/create-admin-roles-list-route.js +3 -2
  40. package/dist/routes/create-admin-user-edit-route.js +4 -3
  41. package/dist/routes/create-admin-users-list-route.js +3 -2
  42. package/dist/routes/create-collection-api-route.d.ts +1 -7
  43. package/dist/routes/create-collection-api-route.js +12 -8
  44. package/dist/routes/create-collection-create-route.js +4 -3
  45. package/dist/routes/create-collection-edit-route.d.ts +1 -7
  46. package/dist/routes/create-collection-edit-route.js +9 -6
  47. package/dist/routes/create-collection-history-route.d.ts +1 -7
  48. package/dist/routes/create-collection-history-route.js +10 -7
  49. package/dist/routes/create-collection-list-route.js +3 -2
  50. package/dist/routes/create-sign-in-route.js +3 -2
  51. package/dist/routes/get-content-locale-route-config.d.ts +12 -0
  52. package/dist/routes/get-content-locale-route-config.js +16 -0
  53. package/dist/routes/index.d.ts +3 -0
  54. package/dist/routes/index.js +2 -0
  55. package/dist/routes/sign-in-path.d.ts +13 -0
  56. package/dist/routes/sign-in-path.js +19 -0
  57. package/dist/server-fns/collections/delete-outcome.d.ts +30 -0
  58. package/dist/server-fns/collections/delete-outcome.js +34 -0
  59. package/dist/server-fns/collections/delete.d.ts +1 -4
  60. package/dist/server-fns/collections/delete.js +2 -4
  61. package/dist/server-fns/collections/index.d.ts +1 -0
  62. package/dist/server-fns/collections/index.js +1 -0
  63. package/dist/server-fns/collections/tree-mutation.d.ts +46 -0
  64. package/dist/server-fns/collections/tree-mutation.js +109 -0
  65. package/dist/server-fns/collections/tree-read.d.ts +6 -0
  66. package/dist/server-fns/collections/tree-read.js +15 -0
  67. package/dist/server-fns/collections/tree.d.ts +5 -4
  68. package/dist/server-fns/collections/tree.js +15 -12
  69. package/dist/server-fns/collections/update.js +2 -1
  70. package/dist/vite/client-hook-boundary.d.ts +19 -0
  71. package/dist/vite/client-hook-boundary.js +20 -0
  72. package/dist/vite/index.d.ts +8 -0
  73. package/dist/vite/index.js +1 -0
  74. package/package.json +15 -9
  75. package/src/admin-shell/admin-activity/list.tsx +13 -3
  76. package/src/admin-shell/admin-roles/delete.tsx +2 -1
  77. package/src/admin-shell/admin-roles/list.tsx +2 -1
  78. package/src/admin-shell/admin-users/delete.tsx +3 -2
  79. package/src/admin-shell/admin-users/list.tsx +9 -8
  80. package/src/admin-shell/chrome/admin-app-bar.tsx +2 -1
  81. package/src/admin-shell/chrome/admin-menu-drawer.tsx +13 -8
  82. package/src/admin-shell/chrome/admin-menu-provider.tsx +2 -1
  83. package/src/admin-shell/chrome/dashboard.tsx +4 -3
  84. package/src/admin-shell/chrome/sign-in-page.tsx +3 -3
  85. package/src/admin-shell/collections/create.tsx +4 -3
  86. package/src/admin-shell/collections/document-history.tsx +5 -0
  87. package/src/admin-shell/collections/edit.tsx +20 -13
  88. package/src/admin-shell/collections/history.tsx +6 -5
  89. package/src/admin-shell/collections/list.tsx +8 -7
  90. package/src/admin-shell/collections/restore-version-modal.tsx +2 -1
  91. package/src/admin-shell/collections/tree-list.tsx +78 -22
  92. package/src/admin-shell/collections/view-menu.tsx +9 -8
  93. package/src/auth/auth-context.test.node.ts +87 -0
  94. package/src/auth/auth-context.ts +18 -0
  95. package/src/auth/request-scope.test.node.ts +93 -0
  96. package/src/auth/request-scope.ts +62 -0
  97. package/src/integrations/abilities.tsx +14 -4
  98. package/src/integrations/byline-client.ts +6 -4
  99. package/src/integrations/byline-public-client.ts +18 -4
  100. package/src/integrations/byline-viewer-client.ts +30 -18
  101. package/src/integrations/request-context-contract.test.node.ts +131 -0
  102. package/src/integrations/request-context-factories.test.node.ts +140 -0
  103. package/src/integrations/start-errors.test.node.ts +22 -0
  104. package/src/integrations/start-errors.ts +1 -0
  105. package/src/routes/admin-path.test.node.ts +129 -0
  106. package/src/routes/admin-path.ts +66 -0
  107. package/src/routes/auth-routes.test.node.ts +39 -0
  108. package/src/routes/create-admin-account-route.tsx +3 -2
  109. package/src/routes/create-admin-activity-route.tsx +3 -2
  110. package/src/routes/create-admin-dashboard-route.tsx +2 -1
  111. package/src/routes/create-admin-layout-route.tsx +4 -4
  112. package/src/routes/create-admin-permissions-route.tsx +3 -2
  113. package/src/routes/create-admin-role-edit-route.tsx +7 -3
  114. package/src/routes/create-admin-roles-list-route.tsx +3 -2
  115. package/src/routes/create-admin-user-edit-route.tsx +7 -3
  116. package/src/routes/create-admin-users-list-route.tsx +3 -2
  117. package/src/routes/create-collection-api-route.tsx +15 -14
  118. package/src/routes/create-collection-create-route.tsx +7 -3
  119. package/src/routes/create-collection-edit-route.tsx +12 -12
  120. package/src/routes/create-collection-history-route.tsx +13 -13
  121. package/src/routes/create-collection-list-route.tsx +3 -2
  122. package/src/routes/create-sign-in-route.tsx +9 -3
  123. package/src/routes/get-content-locale-route-config.ts +26 -0
  124. package/src/routes/index.ts +11 -0
  125. package/src/routes/sign-in-path.test.node.ts +79 -0
  126. package/src/routes/sign-in-path.ts +36 -0
  127. package/src/server-fns/collections/delete-outcome.test.node.ts +72 -0
  128. package/src/server-fns/collections/delete-outcome.ts +80 -0
  129. package/src/server-fns/collections/delete.ts +2 -1
  130. package/src/server-fns/collections/get.ts +2 -0
  131. package/src/server-fns/collections/index.ts +1 -0
  132. package/src/server-fns/collections/tree-mutation.test.node.ts +225 -0
  133. package/src/server-fns/collections/tree-mutation.ts +165 -0
  134. package/src/server-fns/collections/tree-read.test.node.ts +46 -0
  135. package/src/server-fns/collections/tree-read.ts +26 -0
  136. package/src/server-fns/collections/tree.ts +26 -10
  137. package/src/server-fns/collections/update.ts +3 -0
  138. package/src/vite/client-hook-boundary.test.node.ts +50 -0
  139. package/src/vite/client-hook-boundary.ts +45 -0
  140. package/src/vite/index.ts +9 -0
@@ -43,18 +43,18 @@ import { BylineAiAdminProvider } from '../integrations/byline-ai.js'
43
43
  import { bylineFieldServices } from '../integrations/byline-field-services.js'
44
44
  import { getCurrentAdminUser } from '../server-fns/auth/index.js'
45
45
  import { getActiveLocaleFn, setInterfaceLocaleFn } from '../server-fns/i18n/index.js'
46
+ import { createSignInRoutePathResolver } from './sign-in-path.js'
46
47
 
47
48
  interface AdminLayoutOpts {
48
- /** Path users are redirected to when unauthenticated. Defaults to `/sign-in`. */
49
+ /** @deprecated Configure `routes.signIn`; an override must resolve to the same path. */
49
50
  signInPath?: string
50
51
  }
51
52
 
52
53
  export function createAdminLayoutRoute(path: string, opts: AdminLayoutOpts = {}) {
53
- const signInPath = opts.signInPath ?? '/sign-in'
54
-
55
- // biome-ignore lint/suspicious/noExplicitAny: dynamic path bypasses route-tree typing
54
+ const resolveSignInPath = createSignInRoutePathResolver(opts.signInPath)
56
55
  const Route: any = createFileRoute(path as never)({
57
56
  beforeLoad: async ({ location }: { location: { href: string } }) => {
57
+ const signInPath = resolveSignInPath()
58
58
  try {
59
59
  const user = await getCurrentAdminUser()
60
60
  // Resolve the active interface locale once on the server so SSR
@@ -16,6 +16,7 @@ import {
16
16
  type ListRegisteredAbilitiesResponse,
17
17
  listRegisteredAbilities,
18
18
  } from '../server-fns/admin-permissions/index.js'
19
+ import { getAdminRoutePath } from './admin-path.js'
19
20
 
20
21
  export function createAdminPermissionsRoute(path: string) {
21
22
  // biome-ignore lint/suspicious/noExplicitAny: dynamic path bypasses route-tree typing
@@ -31,8 +32,8 @@ export function createAdminPermissionsRoute(path: string) {
31
32
  <>
32
33
  <BreadcrumbsClient
33
34
  breadcrumbs={[
34
- { label: t('chrome.menu.dashboard'), href: '/admin' },
35
- { label: t('chrome.menu.permissions'), href: '/admin/permissions' },
35
+ { label: t('chrome.menu.dashboard'), href: getAdminRoutePath() },
36
+ { label: t('chrome.menu.permissions'), href: getAdminRoutePath('permissions') },
36
37
  ]}
37
38
  />
38
39
  <AbilitiesInspector data={data} />
@@ -19,6 +19,7 @@ import {
19
19
  listRegisteredAbilities,
20
20
  } from '../server-fns/admin-permissions/index.js'
21
21
  import { type AdminRoleResponse, getAdminRole } from '../server-fns/admin-roles/index.js'
22
+ import { getAdminRoutePath } from './admin-path.js'
22
23
 
23
24
  interface LoaderData {
24
25
  role: AdminRoleResponse
@@ -62,9 +63,12 @@ export function createAdminRoleEditRoute(path: string) {
62
63
  <>
63
64
  <BreadcrumbsClient
64
65
  breadcrumbs={[
65
- { label: t('chrome.menu.dashboard'), href: '/admin' },
66
- { label: t('chrome.menu.adminRoles'), href: '/admin/roles' },
67
- { label: t('adminRoles.breadcrumbDetail'), href: `/admin/roles/${role.id}` },
66
+ { label: t('chrome.menu.dashboard'), href: getAdminRoutePath() },
67
+ { label: t('chrome.menu.adminRoles'), href: getAdminRoutePath('roles') },
68
+ {
69
+ label: t('adminRoles.breadcrumbDetail'),
70
+ href: getAdminRoutePath('roles', role.id),
71
+ },
68
72
  ]}
69
73
  />
70
74
  <Section className="py-5 pb-2">
@@ -13,6 +13,7 @@ import { useTranslation } from '@byline/i18n/react'
13
13
  import { AdminRolesListView } from '../admin-shell/admin-roles/list.js'
14
14
  import { BreadcrumbsClient } from '../admin-shell/chrome/breadcrumbs/breadcrumbs-client.js'
15
15
  import { type AdminRoleListResponse, listAdminRoles } from '../server-fns/admin-roles/index.js'
16
+ import { getAdminRoutePath } from './admin-path.js'
16
17
 
17
18
  export function createAdminRolesListRoute(path: string) {
18
19
  // biome-ignore lint/suspicious/noExplicitAny: dynamic path bypasses route-tree typing
@@ -28,8 +29,8 @@ export function createAdminRolesListRoute(path: string) {
28
29
  <>
29
30
  <BreadcrumbsClient
30
31
  breadcrumbs={[
31
- { label: t('chrome.menu.dashboard'), href: '/admin' },
32
- { label: t('chrome.menu.adminRoles'), href: '/admin/roles' },
32
+ { label: t('chrome.menu.dashboard'), href: getAdminRoutePath() },
33
+ { label: t('chrome.menu.adminRoles'), href: getAdminRoutePath('roles') },
33
34
  ]}
34
35
  />
35
36
  <AdminRolesListView data={data} />
@@ -19,6 +19,7 @@ import {
19
19
  getAdminUser,
20
20
  getUserRoles,
21
21
  } from '../server-fns/admin-users/index.js'
22
+ import { getAdminRoutePath } from './admin-path.js'
22
23
 
23
24
  interface LoaderData {
24
25
  user: AdminUserResponse
@@ -79,9 +80,12 @@ export function createAdminUserEditRoute(path: string) {
79
80
  <>
80
81
  <BreadcrumbsClient
81
82
  breadcrumbs={[
82
- { label: t('chrome.menu.dashboard'), href: '/admin' },
83
- { label: t('chrome.menu.adminUsers'), href: '/admin/users' },
84
- { label: t('adminUsers.breadcrumbDetail'), href: `/admin/users/${user.id}` },
83
+ { label: t('chrome.menu.dashboard'), href: getAdminRoutePath() },
84
+ { label: t('chrome.menu.adminUsers'), href: getAdminRoutePath('users') },
85
+ {
86
+ label: t('adminUsers.breadcrumbDetail'),
87
+ href: getAdminRoutePath('users', user.id),
88
+ },
85
89
  ]}
86
90
  />
87
91
  <Section className="py-5 pb-2">
@@ -14,6 +14,7 @@ import { z } from 'zod'
14
14
  import { AdminUsersListView } from '../admin-shell/admin-users/list.js'
15
15
  import { BreadcrumbsClient } from '../admin-shell/chrome/breadcrumbs/breadcrumbs-client.js'
16
16
  import { type AdminUserListResponse, listAdminUsers } from '../server-fns/admin-users/index.js'
17
+ import { getAdminRoutePath } from './admin-path.js'
17
18
 
18
19
  const orderSchema = z.enum([
19
20
  'given_name',
@@ -72,8 +73,8 @@ export function createAdminUsersListRoute(path: string) {
72
73
  <>
73
74
  <BreadcrumbsClient
74
75
  breadcrumbs={[
75
- { label: t('chrome.menu.dashboard'), href: '/admin' },
76
- { label: t('chrome.menu.adminUsers'), href: '/admin/users' },
76
+ { label: t('chrome.menu.dashboard'), href: getAdminRoutePath() },
77
+ { label: t('chrome.menu.adminUsers'), href: getAdminRoutePath('users') },
77
78
  ]}
78
79
  />
79
80
  <AdminUsersListView data={data} />
@@ -16,7 +16,8 @@ import { z } from 'zod'
16
16
  import { BreadcrumbsClient } from '../admin-shell/chrome/breadcrumbs/breadcrumbs-client.js'
17
17
  import { ApiView } from '../admin-shell/collections/api.js'
18
18
  import { getCollectionDocument } from '../server-fns/collections/index.js'
19
- import type { ContentLocaleOption } from '../admin-shell/collections/view-menu.js'
19
+ import { getAdminRoutePath } from './admin-path.js'
20
+ import { getContentLocaleRouteConfig } from './get-content-locale-route-config.js'
20
21
 
21
22
  const searchSchema = z.object({
22
23
  locale: z.string().optional(),
@@ -28,12 +29,7 @@ const searchSchema = z.object({
28
29
  depth: z.coerce.number().int().min(0).max(3).optional(),
29
30
  })
30
31
 
31
- interface CollectionApiOpts {
32
- contentLocales: ReadonlyArray<ContentLocaleOption>
33
- defaultContentLocale: string
34
- }
35
-
36
- export function createCollectionApiRoute(path: string, opts: CollectionApiOpts) {
32
+ export function createCollectionApiRoute(path: string) {
37
33
  // biome-ignore lint/suspicious/noExplicitAny: dynamic path bypasses route-tree typing
38
34
  const Route: any = createFileRoute(path as never)({
39
35
  validateSearch: searchSchema,
@@ -56,7 +52,8 @@ export function createCollectionApiRoute(path: string, opts: CollectionApiOpts)
56
52
  // 'all' is now always explicit in the URL when the user picks it.
57
53
  // No locale param means the user hasn't made a selection yet — default
58
54
  // to the content default locale (same as History and Edit behaviours).
59
- const resolvedLocale = deps.locale ?? opts.defaultContentLocale
55
+ const { defaultContentLocale } = getContentLocaleRouteConfig()
56
+ const resolvedLocale = deps.locale ?? defaultContentLocale
60
57
  const data = await getCollectionDocument(
61
58
  params.collection,
62
59
  params.id,
@@ -79,20 +76,24 @@ export function createCollectionApiRoute(path: string, opts: CollectionApiOpts)
79
76
  const { locale, depth } = Route.useSearch() as z.infer<typeof searchSchema>
80
77
  const collectionDef = getCollectionDefinition(collection) as CollectionDefinition
81
78
  const { t } = useTranslation('byline-admin')
79
+ const { contentLocales, defaultContentLocale } = getContentLocaleRouteConfig()
82
80
 
83
81
  return (
84
82
  <>
85
83
  <BreadcrumbsClient
86
84
  breadcrumbs={[
87
- { label: t('chrome.menu.dashboard'), href: `/admin` },
88
- { label: collectionDef.labels.plural, href: `/admin/collections/${collection}` },
85
+ { label: t('chrome.menu.dashboard'), href: getAdminRoutePath() },
86
+ {
87
+ label: collectionDef.labels.plural,
88
+ href: getAdminRoutePath('collections', collection),
89
+ },
89
90
  {
90
91
  label: t('common.actions.edit'),
91
- href: `/admin/collections/${collection}/${id}`,
92
+ href: getAdminRoutePath('collections', collection, id),
92
93
  },
93
94
  {
94
95
  label: t('collections.viewMenu.apiButton'),
95
- href: `/admin/collections/${collection}/${id}/api`,
96
+ href: getAdminRoutePath('collections', collection, id, 'api'),
96
97
  },
97
98
  ]}
98
99
  />
@@ -101,8 +102,8 @@ export function createCollectionApiRoute(path: string, opts: CollectionApiOpts)
101
102
  initialData={data}
102
103
  locale={locale}
103
104
  depth={depth}
104
- contentLocales={opts.contentLocales}
105
- defaultContentLocale={opts.defaultContentLocale}
105
+ contentLocales={contentLocales}
106
+ defaultContentLocale={defaultContentLocale}
106
107
  />
107
108
  </>
108
109
  )
@@ -18,6 +18,7 @@ import { useTranslation } from '@byline/i18n/react'
18
18
 
19
19
  import { BreadcrumbsClient } from '../admin-shell/chrome/breadcrumbs/breadcrumbs-client.js'
20
20
  import { CreateView } from '../admin-shell/collections/create.js'
21
+ import { getAdminRoutePath } from './admin-path.js'
21
22
 
22
23
  export function createCollectionCreateRoute(path: string) {
23
24
  // biome-ignore lint/suspicious/noExplicitAny: dynamic path bypasses route-tree typing
@@ -49,11 +50,14 @@ export function createCollectionCreateRoute(path: string) {
49
50
  <>
50
51
  <BreadcrumbsClient
51
52
  breadcrumbs={[
52
- { label: t('chrome.menu.dashboard'), href: '/admin' },
53
- { label: collectionDef.labels.plural, href: `/admin/collections/${collection}` },
53
+ { label: t('chrome.menu.dashboard'), href: getAdminRoutePath() },
54
+ {
55
+ label: collectionDef.labels.plural,
56
+ href: getAdminRoutePath('collections', collection),
57
+ },
54
58
  {
55
59
  label: t('collections.breadcrumbs.create'),
56
- href: `/admin/collections/${collection}/create`,
60
+ href: getAdminRoutePath('collections', collection, 'create'),
57
61
  },
58
62
  ]}
59
63
  />
@@ -19,7 +19,8 @@ import { BreadcrumbsClient } from '../admin-shell/chrome/breadcrumbs/breadcrumbs
19
19
  import { useNavigate } from '../admin-shell/chrome/loose-router.js'
20
20
  import { EditView } from '../admin-shell/collections/edit.js'
21
21
  import { getCollectionDocument } from '../server-fns/collections/index.js'
22
- import type { ContentLocaleOption } from '../admin-shell/collections/view-menu.js'
22
+ import { getAdminRoutePath } from './admin-path.js'
23
+ import { getContentLocaleRouteConfig } from './get-content-locale-route-config.js'
23
24
 
24
25
  const searchSchema = z.object({
25
26
  locale: z.string().optional(),
@@ -27,12 +28,7 @@ const searchSchema = z.object({
27
28
  action: z.enum(['created']).optional(),
28
29
  })
29
30
 
30
- interface CollectionEditOpts {
31
- contentLocales: ReadonlyArray<ContentLocaleOption>
32
- defaultContentLocale: string
33
- }
34
-
35
- export function createCollectionEditRoute(path: string, opts: CollectionEditOpts) {
31
+ export function createCollectionEditRoute(path: string) {
36
32
  // biome-ignore lint/suspicious/noExplicitAny: dynamic path bypasses route-tree typing
37
33
  const Route: any = createFileRoute(path as never)({
38
34
  validateSearch: searchSchema,
@@ -82,6 +78,7 @@ export function createCollectionEditRoute(path: string, opts: CollectionEditOpts
82
78
  const { t } = useTranslation('byline-admin')
83
79
  const toastManager = useToastManager()
84
80
  const navigate = useNavigate()
81
+ const { contentLocales, defaultContentLocale } = getContentLocaleRouteConfig()
85
82
 
86
83
  // Post-create toast for the create → edit redirect (?action=created).
87
84
  // Ref-guarded for the same reason as the list route's created toast:
@@ -120,11 +117,14 @@ export function createCollectionEditRoute(path: string, opts: CollectionEditOpts
120
117
  <>
121
118
  <BreadcrumbsClient
122
119
  breadcrumbs={[
123
- { label: t('chrome.menu.dashboard'), href: `/admin` },
124
- { label: collectionDef.labels.plural, href: `/admin/collections/${collection}` },
120
+ { label: t('chrome.menu.dashboard'), href: getAdminRoutePath() },
121
+ {
122
+ label: collectionDef.labels.plural,
123
+ href: getAdminRoutePath('collections', collection),
124
+ },
125
125
  {
126
126
  label: t('common.actions.edit'),
127
- href: `/admin/collections/${collection}/${id}`,
127
+ href: getAdminRoutePath('collections', collection, id),
128
128
  },
129
129
  ]}
130
130
  />
@@ -133,8 +133,8 @@ export function createCollectionEditRoute(path: string, opts: CollectionEditOpts
133
133
  adminConfig={adminConfig ?? undefined}
134
134
  initialData={data}
135
135
  locale={locale}
136
- contentLocales={opts.contentLocales}
137
- defaultContentLocale={opts.defaultContentLocale}
136
+ contentLocales={contentLocales}
137
+ defaultContentLocale={defaultContentLocale}
138
138
  />
139
139
  </>
140
140
  )
@@ -24,7 +24,8 @@ import {
24
24
  getCollectionDocumentAuditLog,
25
25
  getCollectionDocumentHistory,
26
26
  } from '../server-fns/collections/index.js'
27
- import type { ContentLocaleOption } from '../admin-shell/collections/view-menu.js'
27
+ import { getAdminRoutePath } from './admin-path.js'
28
+ import { getContentLocaleRouteConfig } from './get-content-locale-route-config.js'
28
29
 
29
30
  const searchSchema = z.object({
30
31
  page: z.coerce.number().min(1).optional(),
@@ -43,12 +44,7 @@ const searchSchema = z.object({
43
44
  // than wiring a second, tab-specific pager into the shared history route.
44
45
  const AUDIT_LOG_PAGE_SIZE = 100
45
46
 
46
- interface CollectionHistoryOpts {
47
- contentLocales: ReadonlyArray<ContentLocaleOption>
48
- defaultContentLocale: string
49
- }
50
-
51
- export function createCollectionHistoryRoute(path: string, opts: CollectionHistoryOpts) {
47
+ export function createCollectionHistoryRoute(path: string) {
52
48
  // biome-ignore lint/suspicious/noExplicitAny: dynamic path bypasses route-tree typing
53
49
  const Route: any = createFileRoute(path as never)({
54
50
  validateSearch: searchSchema,
@@ -118,20 +114,24 @@ export function createCollectionHistoryRoute(path: string, opts: CollectionHisto
118
114
  const collectionDef = getCollectionDefinition(collection) as CollectionDefinition
119
115
  const adminConfig = getCollectionAdminConfig(collection)
120
116
  const { t } = useTranslation('byline-admin')
117
+ const { contentLocales, defaultContentLocale } = getContentLocaleRouteConfig()
121
118
 
122
119
  return (
123
120
  <>
124
121
  <BreadcrumbsClient
125
122
  breadcrumbs={[
126
- { label: t('chrome.menu.dashboard'), href: `/admin` },
127
- { label: collectionDef.labels.plural, href: `/admin/collections/${collection}` },
123
+ { label: t('chrome.menu.dashboard'), href: getAdminRoutePath() },
124
+ {
125
+ label: collectionDef.labels.plural,
126
+ href: getAdminRoutePath('collections', collection),
127
+ },
128
128
  {
129
129
  label: t('common.actions.edit'),
130
- href: `/admin/collections/${collection}/${Route.useParams().id}`,
130
+ href: getAdminRoutePath('collections', collection, Route.useParams().id),
131
131
  },
132
132
  {
133
133
  label: t('collections.breadcrumbs.history'),
134
- href: `/admin/collections/${collection}/${Route.useParams().id}/history`,
134
+ href: getAdminRoutePath('collections', collection, Route.useParams().id, 'history'),
135
135
  },
136
136
  ]}
137
137
  />
@@ -142,8 +142,8 @@ export function createCollectionHistoryRoute(path: string, opts: CollectionHisto
142
142
  data={history}
143
143
  auditLog={auditLog}
144
144
  currentDocument={currentDocument as Record<string, unknown> | null}
145
- contentLocales={opts.contentLocales}
146
- defaultContentLocale={opts.defaultContentLocale}
145
+ contentLocales={contentLocales}
146
+ defaultContentLocale={defaultContentLocale}
147
147
  />
148
148
  </>
149
149
  )
@@ -30,6 +30,7 @@ import {
30
30
  placeTreeNode,
31
31
  reorderCollectionDocument,
32
32
  } from '../server-fns/collections/index.js'
33
+ import { getAdminRoutePath } from './admin-path.js'
33
34
  import type { CollectionTreeRow } from '../server-fns/collections/tree.js'
34
35
 
35
36
  const searchSchema = z.object({
@@ -167,10 +168,10 @@ export function createCollectionListRoute(path: string) {
167
168
  <>
168
169
  <BreadcrumbsClient
169
170
  breadcrumbs={[
170
- { label: t('chrome.menu.dashboard'), href: `/admin` },
171
+ { label: t('chrome.menu.dashboard'), href: getAdminRoutePath() },
171
172
  {
172
173
  label: data.included.collection.labels.plural,
173
- href: `/admin/collections/${collection}`,
174
+ href: getAdminRoutePath('collections', collection),
174
175
  },
175
176
  ]}
176
177
  />
@@ -9,7 +9,7 @@
9
9
  /**
10
10
  * Sign-in page route factory.
11
11
  *
12
- * Lives outside the `/admin` subtree so the authenticated-admin layout
12
+ * Lives outside the configured admin subtree so the authenticated-admin layout
13
13
  * (and its `beforeLoad` guard) does not apply. Preserves a `callbackUrl`
14
14
  * query param so sign-ins triggered by the admin guard redirect back to
15
15
  * the originally-requested path.
@@ -21,6 +21,7 @@ import type { LocaleCode } from '@byline/i18n'
21
21
 
22
22
  import { SignInPage } from '../admin-shell/chrome/sign-in-page.js'
23
23
  import { getActiveLocaleFn } from '../server-fns/i18n/index.js'
24
+ import { resolveAdminCallbackPath, resolveAdminSignInRedirect } from './admin-path.js'
24
25
 
25
26
  interface SignInSearch {
26
27
  callbackUrl?: string
@@ -30,7 +31,7 @@ export function createSignInRoute(path: string) {
30
31
  // biome-ignore lint/suspicious/noExplicitAny: dynamic path bypasses route-tree typing
31
32
  const Route: any = createFileRoute(path as never)({
32
33
  validateSearch: (search: Record<string, unknown>): SignInSearch => {
33
- const callbackUrl = typeof search.callbackUrl === 'string' ? search.callbackUrl : undefined
34
+ const callbackUrl = resolveAdminCallbackPath(search.callbackUrl)
34
35
  return { callbackUrl }
35
36
  },
36
37
  beforeLoad: async () => {
@@ -45,7 +46,12 @@ export function createSignInRoute(path: string) {
45
46
  component: function SignInRouteComponent() {
46
47
  const { callbackUrl } = Route.useSearch() as SignInSearch
47
48
  const { activeLocale } = Route.useRouteContext() as { activeLocale: LocaleCode }
48
- return <SignInPage callbackUrl={callbackUrl} activeLocale={activeLocale} />
49
+ return (
50
+ <SignInPage
51
+ redirectTo={resolveAdminSignInRedirect(callbackUrl)}
52
+ activeLocale={activeLocale}
53
+ />
54
+ )
49
55
  },
50
56
  })
51
57
  return Route
@@ -0,0 +1,26 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ import { getClientConfig } from '@byline/core'
10
+
11
+ import type { ContentLocaleOption } from '../admin-shell/collections/view-menu.js'
12
+
13
+ export function getContentLocaleRouteConfig(): {
14
+ contentLocales: ContentLocaleOption[]
15
+ defaultContentLocale: string
16
+ } {
17
+ const content = getClientConfig().i18n.content
18
+ const labels = new Map(
19
+ content.localeDefinitions?.map(({ code, nativeName }) => [code, nativeName]) ?? []
20
+ )
21
+
22
+ return {
23
+ contentLocales: content.locales.map((code) => ({ code, label: labels.get(code) ?? code })),
24
+ defaultContentLocale: content.defaultLocale,
25
+ }
26
+ }
@@ -6,6 +6,16 @@
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
8
 
9
+ export {
10
+ getAdminRouteId,
11
+ getAdminRoutePath,
12
+ isAdminRoutePathActive,
13
+ isRoutePathWithin,
14
+ resolveAdminCallbackPath,
15
+ resolveAdminSignInRedirect,
16
+ } from './admin-path.js'
17
+ export { configureSignInRoutePath, getSignInRoutePath } from './sign-in-path.js'
18
+
9
19
  /**
10
20
  * Route factories for the Byline admin UI on TanStack Start.
11
21
  *
@@ -14,6 +24,7 @@
14
24
  * `createFileRoute(path)({...})`. The host's route file collapses to:
15
25
  *
16
26
  * import { createCollectionListRoute } from '@byline/host-tanstack-start/routes'
27
+ * // Default admin mount example:
17
28
  * export const Route = createCollectionListRoute('/_byline/admin/collections/$collection/')
18
29
  *
19
30
  * Routes that need host-specific data (i18n bridge component, content
@@ -0,0 +1,79 @@
1
+ import { defineClientConfig } from '@byline/core'
2
+ import { beforeEach, describe, expect, it } from 'vitest'
3
+
4
+ import {
5
+ configureSignInRoutePath,
6
+ createSignInRoutePathResolver,
7
+ getSignInRoutePath,
8
+ } from './sign-in-path.js'
9
+
10
+ describe('sign-in route configuration', () => {
11
+ beforeEach(() => {
12
+ defineClientConfig({
13
+ serverURL: 'http://localhost:3000',
14
+ routes: { signIn: '/staff/login/' },
15
+ collections: [],
16
+ admin: [],
17
+ i18n: {
18
+ interface: { defaultLocale: 'en', locales: [] },
19
+ content: { defaultLocale: 'en', locales: [] },
20
+ },
21
+ })
22
+ })
23
+
24
+ it('reads and normalizes routes.signIn', () => {
25
+ expect(getSignInRoutePath()).toBe('/staff/login')
26
+ })
27
+
28
+ it('allows a deprecated signInPath override when it matches canonical config', () => {
29
+ expect(configureSignInRoutePath('staff/login/')).toBe('/staff/login')
30
+ })
31
+
32
+ it('rejects an invalid or divergent deprecated override when resolved', () => {
33
+ expect(() => configureSignInRoutePath('/auth/login/')).toThrow(/must match routes\.signIn/)
34
+ expect(() => configureSignInRoutePath('/auth/../login')).toThrow(/routes\.signIn/)
35
+ })
36
+
37
+ it('validates an override against configured rather than default route trees', () => {
38
+ defineClientConfig({
39
+ serverURL: 'http://localhost:3000',
40
+ routes: { admin: '/cms', api: '/rpc', signIn: '/admin/login' },
41
+ collections: [],
42
+ admin: [],
43
+ i18n: {
44
+ interface: { defaultLocale: 'en', locales: [] },
45
+ content: { defaultLocale: 'en', locales: [] },
46
+ },
47
+ })
48
+
49
+ expect(configureSignInRoutePath('/admin/login/')).toBe('/admin/login')
50
+ expect(() => configureSignInRoutePath('/cms/login')).toThrow(/routes\.signIn/)
51
+ })
52
+
53
+ it('defers a deprecated override until parent beforeLoad config registration', () => {
54
+ defineClientConfig({
55
+ serverURL: 'http://localhost:3000',
56
+ routes: { signIn: '/old/login' },
57
+ collections: [],
58
+ admin: [],
59
+ i18n: {
60
+ interface: { defaultLocale: 'en', locales: [] },
61
+ content: { defaultLocale: 'en', locales: [] },
62
+ },
63
+ })
64
+ const resolveSignInPath = createSignInRoutePathResolver('/staff/login/')
65
+
66
+ defineClientConfig({
67
+ serverURL: 'http://localhost:3000',
68
+ routes: { signIn: '/staff/login' },
69
+ collections: [],
70
+ admin: [],
71
+ i18n: {
72
+ interface: { defaultLocale: 'en', locales: [] },
73
+ content: { defaultLocale: 'en', locales: [] },
74
+ },
75
+ })
76
+
77
+ expect(resolveSignInPath()).toBe('/staff/login')
78
+ })
79
+ })
@@ -0,0 +1,36 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ import { getClientConfig, resolveRoutes } from '@byline/core'
10
+
11
+ /** Read the canonical client-safe sign-in destination. */
12
+ export function getSignInRoutePath(): string {
13
+ return getClientConfig().routes.signIn
14
+ }
15
+
16
+ /** @deprecated Configure `routes.signIn`; retained for route-factory input validation. */
17
+ export function configureSignInRoutePath(legacyOverride?: string): string {
18
+ const routes = getClientConfig().routes
19
+ const configured = routes.signIn
20
+ if (legacyOverride === undefined) return configured
21
+
22
+ const normalizedOverride = resolveRoutes({ ...routes, signIn: legacyOverride }).signIn
23
+ if (normalizedOverride !== configured) {
24
+ throw new Error(
25
+ `createAdminLayoutRoute signInPath (${normalizedOverride}) must match routes.signIn (${configured})`
26
+ )
27
+ }
28
+ return configured
29
+ }
30
+
31
+ /** Defer deprecated override validation until the admin route's beforeLoad boundary. */
32
+ export function createSignInRoutePathResolver(legacyOverride?: string): () => string {
33
+ return legacyOverride === undefined
34
+ ? getSignInRoutePath
35
+ : () => configureSignInRoutePath(legacyOverride)
36
+ }