@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
@@ -28,6 +28,7 @@ import {
28
28
  } from '@byline/ui/react'
29
29
  import cx from 'classnames'
30
30
 
31
+ import { getAdminRoutePath } from '../../routes/admin-path.js'
31
32
  import { Link, useNavigate } from '../chrome/loose-router.js'
32
33
  import { RouterPager } from '../chrome/router-pager.js'
33
34
  import {
@@ -45,7 +46,7 @@ import type {
45
46
  // are fixed at module scope; the `label` field is filled in per-render
46
47
  // inside the component so it can flow through `t(...)`. Same pattern the
47
48
  // account container's `panels` map uses.
48
- type ColumnTemplate = Omit<TableHeadingCellSortableProps, 'lng' | 'label'> & {
49
+ type ColumnTemplate = Omit<TableHeadingCellSortableProps, 'lng' | 'label' | 'path'> & {
49
50
  labelKey: string
50
51
  }
51
52
 
@@ -53,7 +54,6 @@ const columnTemplates: ColumnTemplate[] = [
53
54
  {
54
55
  fieldName: 'given_name',
55
56
  labelKey: 'adminUsers.list.columns.givenName',
56
- path: '/admin/users',
57
57
  sortable: true,
58
58
  scope: 'col',
59
59
  align: 'left',
@@ -62,7 +62,6 @@ const columnTemplates: ColumnTemplate[] = [
62
62
  {
63
63
  fieldName: 'family_name',
64
64
  labelKey: 'adminUsers.list.columns.familyName',
65
- path: '/admin/users',
66
65
  sortable: true,
67
66
  scope: 'col',
68
67
  align: 'left',
@@ -71,7 +70,6 @@ const columnTemplates: ColumnTemplate[] = [
71
70
  {
72
71
  fieldName: 'email',
73
72
  labelKey: 'adminUsers.list.columns.email',
74
- path: '/admin/users',
75
73
  sortable: true,
76
74
  scope: 'col',
77
75
  align: 'left',
@@ -80,7 +78,6 @@ const columnTemplates: ColumnTemplate[] = [
80
78
  {
81
79
  fieldName: 'updated_at',
82
80
  labelKey: 'adminUsers.list.columns.updated',
83
- path: '/admin/users',
84
81
  sortable: true,
85
82
  scope: 'col',
86
83
  align: 'right',
@@ -89,7 +86,6 @@ const columnTemplates: ColumnTemplate[] = [
89
86
  {
90
87
  fieldName: 'created_at',
91
88
  labelKey: 'adminUsers.list.columns.created',
92
- path: '/admin/users',
93
89
  sortable: true,
94
90
  scope: 'col',
95
91
  align: 'right',
@@ -136,7 +132,12 @@ export function AdminUsersListView({ data }: { data: AdminUserListResponse }) {
136
132
  const [isCreateDrawerOpen, setIsCreateDrawerOpen] = useState(false)
137
133
 
138
134
  const tableColumnDefs = useMemo(
139
- () => columnTemplates.map(({ labelKey, ...rest }) => ({ ...rest, label: t(labelKey) })),
135
+ () =>
136
+ columnTemplates.map(({ labelKey, ...rest }) => ({
137
+ ...rest,
138
+ label: t(labelKey),
139
+ path: getAdminRoutePath('users'),
140
+ })),
140
141
  [t]
141
142
  )
142
143
 
@@ -232,7 +233,7 @@ export function AdminUsersListView({ data }: { data: AdminUserListResponse }) {
232
233
  })}
233
234
  >
234
235
  <Table.Cell>
235
- <Link to={'/admin/users/$id' as never} params={{ id: user.id }}>
236
+ <Link to={getAdminRoutePath('users', '$id')} params={{ id: user.id }}>
236
237
  {user.given_name ?? (
237
238
  <span
238
239
  className={cx('muted byline-admin-users-list-not-set', styles.notSet)}
@@ -14,6 +14,7 @@ import { LanguageMenu, useTranslation } from '@byline/i18n/react'
14
14
  import { Button } from '@byline/ui/react'
15
15
  import cx from 'classnames'
16
16
 
17
+ import { getSignInRoutePath } from '../../routes/sign-in-path.js'
17
18
  import { adminSignOut, type CurrentAdminUser } from '../../server-fns/auth/index.js'
18
19
  import styles from './admin-app-bar.module.css'
19
20
  import { AdminDrawerToggle } from './admin-drawer-toggle.js'
@@ -48,7 +49,7 @@ export function AdminAppBar({ user }: AdminAppBarProps) {
48
49
  console.warn('sign-out request failed', err)
49
50
  }
50
51
  // Full-page navigation so the admin guard re-runs with cleared cookies.
51
- window.location.assign('/sign-in')
52
+ window.location.assign(getSignInRoutePath())
52
53
  }
53
54
 
54
55
  return (
@@ -27,6 +27,11 @@ import cx from 'classnames'
27
27
  import { useSwipeable } from 'react-swipeable'
28
28
 
29
29
  import { useAbilities } from '../../integrations/abilities.jsx'
30
+ import {
31
+ getAdminRoutePath,
32
+ isAdminRoutePathActive,
33
+ isRoutePathWithin,
34
+ } from '../../routes/admin-path.js'
30
35
  import styles from './admin-menu-drawer.module.css'
31
36
  import { useAdminMenu } from './admin-menu-provider.jsx'
32
37
  import { Link } from './loose-router.js'
@@ -35,8 +40,8 @@ import { PreviewToggle } from './preview-toggle.jsx'
35
40
  const isActive = (currentPath: string, linkHref: string): boolean => {
36
41
  // Root admin highlights only on exact match so it doesn't stay lit on
37
42
  // every admin subpath.
38
- if (linkHref === '/admin') return currentPath === '/admin'
39
- return currentPath.startsWith(linkHref)
43
+ if (linkHref === getAdminRoutePath()) return isAdminRoutePathActive(currentPath)
44
+ return isRoutePathWithin(currentPath, linkHref)
40
45
  }
41
46
 
42
47
  interface MenuItemProps {
@@ -119,7 +124,7 @@ export function AdminMenuDrawer(): React.JSX.Element | null {
119
124
  <nav className={cx('byline-admin-menu-drawer admin-menu-drawer', styles.nav)}>
120
125
  <ul>
121
126
  <MenuItem
122
- to="/admin"
127
+ to={getAdminRoutePath()}
123
128
  label={t('chrome.menu.dashboard')}
124
129
  icon={<HomeIcon width="20px" height="20px" />}
125
130
  pathname={pathname}
@@ -130,7 +135,7 @@ export function AdminMenuDrawer(): React.JSX.Element | null {
130
135
  <li className="menu-separator" />
131
136
  {canReadUsers && (
132
137
  <MenuItem
133
- to="/admin/users"
138
+ to={getAdminRoutePath('users')}
134
139
  label={t('chrome.menu.adminUsers')}
135
140
  icon={<UsersIcon width="20px" height="20px" />}
136
141
  pathname={pathname}
@@ -139,7 +144,7 @@ export function AdminMenuDrawer(): React.JSX.Element | null {
139
144
  )}
140
145
  {canReadRoles && (
141
146
  <MenuItem
142
- to="/admin/roles"
147
+ to={getAdminRoutePath('roles')}
143
148
  label={t('chrome.menu.adminRoles')}
144
149
  icon={<RolesIcon width="20px" height="20px" />}
145
150
  pathname={pathname}
@@ -148,7 +153,7 @@ export function AdminMenuDrawer(): React.JSX.Element | null {
148
153
  )}
149
154
  {canReadPermissions && (
150
155
  <MenuItem
151
- to="/admin/permissions"
156
+ to={getAdminRoutePath('permissions')}
152
157
  label={t('chrome.menu.permissions')}
153
158
  icon={<SettingsSlidersIcon width="20px" height="20px" />}
154
159
  pathname={pathname}
@@ -157,7 +162,7 @@ export function AdminMenuDrawer(): React.JSX.Element | null {
157
162
  )}
158
163
  {canReadActivity && (
159
164
  <MenuItem
160
- to="/admin/activity"
165
+ to={getAdminRoutePath('activity')}
161
166
  label={t('chrome.menu.activity')}
162
167
  icon={<ActivityIcon width="20px" height="20px" />}
163
168
  pathname={pathname}
@@ -169,7 +174,7 @@ export function AdminMenuDrawer(): React.JSX.Element | null {
169
174
  <li className="menu-separator" />
170
175
  <PreviewToggle compact={compact} />
171
176
  <MenuItem
172
- to="/admin/account"
177
+ to={getAdminRoutePath('account')}
173
178
  label={t('chrome.account')}
174
179
  icon={<UserIcon />}
175
180
  pathname={pathname}
@@ -12,6 +12,7 @@ import type React from 'react'
12
12
  import { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react'
13
13
  import { useRouterState } from '@tanstack/react-router'
14
14
 
15
+ import { isAdminRoutePathActive } from '../../routes/admin-path.js'
15
16
  import { useMediaQuery } from './use-media-query.js'
16
17
 
17
18
  interface AdminMenuContextType {
@@ -37,7 +38,7 @@ interface AdminMenuProviderProps {
37
38
  * deliberate open stays open.
38
39
  */
39
40
  function isWideRoute(pathname: string): boolean {
40
- return pathname.startsWith('/admin/collections')
41
+ return isAdminRoutePathActive(pathname, 'collections')
41
42
  }
42
43
 
43
44
  export function AdminMenuProvider({ children }: AdminMenuProviderProps): React.JSX.Element {
@@ -12,6 +12,7 @@ import { useTranslation } from '@byline/i18n/react'
12
12
  import { Card, Container, Section } from '@byline/ui/react'
13
13
  import cx from 'classnames'
14
14
 
15
+ import { getAdminRoutePath } from '../../routes/admin-path.js'
15
16
  import styles from './dashboard.module.css'
16
17
  import { Link } from './loose-router.js'
17
18
  import type { CollectionStatusCount } from '../../server-fns/collections/index.js'
@@ -42,7 +43,7 @@ function StatTile({
42
43
  const modifier = tileModifier(ws.name)
43
44
  return (
44
45
  <Link
45
- to={'/admin/collections/$collection' as never}
46
+ to={getAdminRoutePath('collections', '$collection')}
46
47
  params={{ collection: collectionPath }}
47
48
  search={{ status: ws.name }}
48
49
  className={cx('byline-dashboard-stat-tile', styles.statTile, modifier.global, modifier.local)}
@@ -77,7 +78,7 @@ export function AdminDashboard({ statsMap }: AdminDashboardProps) {
77
78
  return (
78
79
  <Card key={collection.path}>
79
80
  <Link
80
- to={'/admin/collections/$collection' as never}
81
+ to={getAdminRoutePath('collections', '$collection')}
81
82
  params={{ collection: collection.path }}
82
83
  className={cx('byline-dashboard-card-link', styles.cardLink)}
83
84
  >
@@ -114,7 +115,7 @@ export function AdminDashboard({ statsMap }: AdminDashboardProps) {
114
115
  </div>
115
116
  ) : (
116
117
  <Link
117
- to={'/admin/collections/$collection' as never}
118
+ to={getAdminRoutePath('collections', '$collection')}
118
119
  params={{ collection: collection.path }}
119
120
  className={cx('byline-dashboard-empty-link', styles.emptyLink)}
120
121
  >
@@ -19,7 +19,7 @@ import { setInterfaceLocaleFn } from '../../server-fns/i18n/index.js'
19
19
  import styles from './sign-in-page.module.css'
20
20
 
21
21
  interface SignInPageProps {
22
- callbackUrl?: string
22
+ redirectTo: string
23
23
  activeLocale: LocaleCode
24
24
  }
25
25
 
@@ -44,7 +44,7 @@ interface SignInPageProps {
44
44
  * the form's action row can render a plain "Home" link beside the submit
45
45
  * button.
46
46
  */
47
- export function SignInPage({ callbackUrl, activeLocale }: SignInPageProps) {
47
+ export function SignInPage({ redirectTo, activeLocale }: SignInPageProps) {
48
48
  const { i18n, serverURL } = getClientConfig()
49
49
  const localeDefinitions = buildLocaleDefinitions(
50
50
  i18n.interface.locales,
@@ -68,7 +68,7 @@ export function SignInPage({ callbackUrl, activeLocale }: SignInPageProps) {
68
68
  <LanguageMenu />
69
69
  </div>
70
70
  <div className={cx('byline-sign-in-page-inner', styles.inner)}>
71
- <SignInForm callbackUrl={callbackUrl} homeUrl={serverURL} />
71
+ <SignInForm redirectTo={redirectTo} homeUrl={serverURL} />
72
72
  </div>
73
73
  </main>
74
74
  </BylineAdminServicesProvider>
@@ -13,6 +13,7 @@ import type { CollectionAdminConfig, CollectionDefinition } from '@byline/core'
13
13
  import { useTranslation } from '@byline/i18n/react'
14
14
  import { Container, Section, useToastManager } from '@byline/ui/react'
15
15
 
16
+ import { getAdminRoutePath } from '../../routes/admin-path.js'
16
17
  import { createCollectionDocument } from '../../server-fns/collections/index.js'
17
18
  import { useNavigate } from '../chrome/loose-router.js'
18
19
  import { useTanStackNavigationGuard } from './tanstack-navigation-guard.js'
@@ -66,13 +67,13 @@ export const CreateView = ({
66
67
  // doesn't return the new document's id.
67
68
  if (result?.documentId) {
68
69
  navigate({
69
- to: '/admin/collections/$collection/$id' as never,
70
+ to: getAdminRoutePath('collections', '$collection', '$id'),
70
71
  params: { collection: path, id: result.documentId } as never,
71
72
  search: { action: 'created' },
72
73
  })
73
74
  } else {
74
75
  navigate({
75
- to: '/admin/collections/$collection' as never,
76
+ to: getAdminRoutePath('collections', '$collection'),
76
77
  params: { collection: path },
77
78
  search: { action: 'created' },
78
79
  })
@@ -117,7 +118,7 @@ export const CreateView = ({
117
118
  useNavigationGuard={useTanStackNavigationGuard}
118
119
  onCancel={() =>
119
120
  navigate({
120
- to: '/admin/collections/$collection' as never,
121
+ to: getAdminRoutePath('collections', '$collection'),
121
122
  params: { collection: path },
122
123
  })
123
124
  }
@@ -52,12 +52,17 @@ const ACTION_KEYS: Record<string, string> = {
52
52
  'document.locales.changed': 'collections.documentHistory.actionLocalesChanged',
53
53
  'document.status.changed': 'collections.documentHistory.actionStatusChanged',
54
54
  'document.deleted': 'collections.documentHistory.actionDeleted',
55
+ 'document.tree.placed': 'collections.documentHistory.actionTreePlaced',
56
+ 'document.tree.reparented': 'collections.documentHistory.actionTreeReparented',
57
+ 'document.tree.reordered': 'collections.documentHistory.actionTreeReordered',
58
+ 'document.tree.removed': 'collections.documentHistory.actionTreeRemoved',
55
59
  }
56
60
 
57
61
  /** Render an audit before/after value inline: arrays comma-join, nullish → em-dash. */
58
62
  function formatAuditValue(value: unknown): string {
59
63
  if (value == null) return '—'
60
64
  if (Array.isArray(value)) return value.length > 0 ? value.join(', ') : '—'
65
+ if (typeof value === 'object') return JSON.stringify(value)
61
66
  return String(value)
62
67
  }
63
68
 
@@ -15,11 +15,13 @@ import type { AnyCollectionSchemaTypes } from '@byline/core/zod-schemas'
15
15
  import { useTranslation } from '@byline/i18n/react'
16
16
  import { Container, Section, useToastManager } from '@byline/ui/react'
17
17
 
18
+ import { getAdminRoutePath } from '../../routes/admin-path.js'
18
19
  import {
19
20
  copyDocumentToLocale,
20
21
  deleteDocument,
21
22
  deleteDocumentLocale,
22
23
  duplicateCollectionDocument,
24
+ hasDeleteSideEffectFailures,
23
25
  unpublishDocument,
24
26
  updateCollectionDocumentSystemFields,
25
27
  updateCollectionDocumentWithPatches,
@@ -73,7 +75,7 @@ export const EditView = ({
73
75
 
74
76
  const handleLocaleChange = (newLocale: string) => {
75
77
  navigate({
76
- to: '/admin/collections/$collection/$id' as never,
78
+ to: getAdminRoutePath('collections', '$collection', '$id'),
77
79
  params: { collection: path, id: String(initialData.id) },
78
80
  search: { locale: newLocale },
79
81
  })
@@ -98,7 +100,7 @@ export const EditView = ({
98
100
  setEditState({ status: 'success', message: description })
99
101
  // Refresh the page to reflect the new status.
100
102
  navigate({
101
- to: '/admin/collections/$collection/$id' as never,
103
+ to: getAdminRoutePath('collections', '$collection', '$id'),
102
104
  params: { collection: path, id: String(initialData.id) },
103
105
  search: (prev: Record<string, unknown>) => ({ ...prev }),
104
106
  })
@@ -149,7 +151,7 @@ export const EditView = ({
149
151
  })
150
152
  setEditState({ status: 'success', message: description })
151
153
  navigate({
152
- to: '/admin/collections/$collection/$id' as never,
154
+ to: getAdminRoutePath('collections', '$collection', '$id'),
153
155
  params: { collection: path, id: String(initialData.id) },
154
156
  search: (prev: Record<string, unknown>) => ({ ...prev }),
155
157
  })
@@ -196,7 +198,7 @@ export const EditView = ({
196
198
  })
197
199
  // Navigate to the new document's edit view.
198
200
  navigate({
199
- to: '/admin/collections/$collection/$id' as never,
201
+ to: getAdminRoutePath('collections', '$collection', '$id'),
200
202
  params: { collection: path, id: result.documentId },
201
203
  })
202
204
  } catch (err) {
@@ -270,7 +272,7 @@ export const EditView = ({
270
272
  // Switch the form to the target locale so the editor sees the
271
273
  // copied content immediately.
272
274
  navigate({
273
- to: '/admin/collections/$collection/$id' as never,
275
+ to: getAdminRoutePath('collections', '$collection', '$id'),
274
276
  params: { collection: path, id: String(initialData.id) },
275
277
  search: { locale: targetLocale },
276
278
  })
@@ -322,7 +324,7 @@ export const EditView = ({
322
324
  // The deleted locale may be the one being viewed — land on the default
323
325
  // locale (which always survives) so the loader re-fetches a valid view.
324
326
  navigate({
325
- to: '/admin/collections/$collection/$id' as never,
327
+ to: getAdminRoutePath('collections', '$collection', '$id'),
326
328
  params: { collection: path, id: String(initialData.id) },
327
329
  search: { locale: defaultContentLocale },
328
330
  })
@@ -347,14 +349,19 @@ export const EditView = ({
347
349
 
348
350
  const handleDelete = async () => {
349
351
  try {
350
- await deleteDocument({ data: { collection: path, id: String(initialData.id) } })
351
- const description = t('collections.edit.deletedDescription', { label: singular })
352
+ const result = await deleteDocument({
353
+ data: { collection: path, id: String(initialData.id) },
354
+ })
355
+ const hasSideEffectFailures = hasDeleteSideEffectFailures(result)
356
+ const description = hasSideEffectFailures
357
+ ? t('collections.edit.deletedWithWarningsDescription', { label: singular })
358
+ : t('collections.edit.deletedDescription', { label: singular })
352
359
  toastManager.add({
353
360
  title: t('collections.edit.deleteTitle', { label: singular }),
354
361
  description,
355
362
  data: {
356
- intent: 'success',
357
- iconType: 'success',
363
+ intent: hasSideEffectFailures ? 'warning' : 'success',
364
+ iconType: hasSideEffectFailures ? 'warning' : 'success',
358
365
  icon: true,
359
366
  close: true,
360
367
  },
@@ -362,7 +369,7 @@ export const EditView = ({
362
369
  setEditState({ status: 'success', message: description })
363
370
  // Navigate back to the collection list after deletion.
364
371
  navigate({
365
- to: '/admin/collections/$collection' as never,
372
+ to: getAdminRoutePath('collections', '$collection'),
366
373
  params: { collection: path },
367
374
  })
368
375
  } catch (err) {
@@ -457,7 +464,7 @@ export const EditView = ({
457
464
  // The new version will have a fresh version ID, draft status, and
458
465
  // updated publishedVersion metadata.
459
466
  navigate({
460
- to: '/admin/collections/$collection/$id' as never,
467
+ to: getAdminRoutePath('collections', '$collection', '$id'),
461
468
  params: { collection: path, id: String(initialData.id) },
462
469
  search: (prev: Record<string, unknown>) => ({ ...prev }),
463
470
  })
@@ -524,7 +531,7 @@ export const EditView = ({
524
531
  workflowStatuses={workflowStatuses}
525
532
  onCancel={() =>
526
533
  navigate({
527
- to: '/admin/collections/$collection' as never,
534
+ to: getAdminRoutePath('collections', '$collection'),
528
535
  params: { collection: path },
529
536
  })
530
537
  }
@@ -26,6 +26,7 @@ import {
26
26
  } from '@byline/ui/react'
27
27
  import cx from 'classnames'
28
28
 
29
+ import { getAdminRouteId, getAdminRoutePath } from '../../routes/admin-path.js'
29
30
  import { Link, useNavigate } from '../chrome/loose-router.js'
30
31
  import { RouterPager } from '../chrome/router-pager.js'
31
32
  import { TableHeadingCellSortable } from '../chrome/th-sortable.js'
@@ -139,8 +140,8 @@ export const HistoryView = ({
139
140
  defaultContentLocale: string
140
141
  }) => {
141
142
  const { id, collection } = useParams({
142
- from: '/_byline/admin/collections/$collection/$id/history',
143
- })
143
+ from: getAdminRouteId('collections', '$collection', '$id', 'history') as never,
144
+ }) as { id: string; collection: string }
144
145
  const navigate = useNavigate()
145
146
  const { getCollectionDocumentVersion } = useBylineAdminServices()
146
147
  const { t } = useTranslation('byline-admin')
@@ -181,7 +182,7 @@ export const HistoryView = ({
181
182
  delete params.tab
182
183
  }
183
184
  navigate({
184
- to: '/admin/collections/$collection/$id/history' as never,
185
+ to: getAdminRoutePath('collections', '$collection', '$id', 'history'),
185
186
  params: { collection, id } as never,
186
187
  search: params,
187
188
  })
@@ -193,7 +194,7 @@ export const HistoryView = ({
193
194
  delete params.page
194
195
  params.page_size = Number.parseInt(value, 10)
195
196
  navigate({
196
- to: '/admin/collections/$collection/$id/history' as never,
197
+ to: getAdminRoutePath('collections', '$collection', '$id', 'history'),
197
198
  params: { collection, id } as never,
198
199
  search: params,
199
200
  })
@@ -382,7 +383,7 @@ export const HistoryView = ({
382
383
  </button>
383
384
  ) : (
384
385
  <Link
385
- to={'/admin/collections/$collection/$id' as never}
386
+ to={getAdminRoutePath('collections', '$collection', '$id')}
386
387
  params={{
387
388
  collection,
388
389
  id: document.id,
@@ -43,6 +43,7 @@ import {
43
43
  } from '@dnd-kit/sortable'
44
44
  import cx from 'classnames'
45
45
 
46
+ import { getAdminRoutePath } from '../../routes/admin-path.js'
46
47
  import { Link, useNavigate } from '../chrome/loose-router.js'
47
48
  import { RouterPager } from '../chrome/router-pager.js'
48
49
  import { SortAscendingIcon } from '../chrome/sort-icons.js'
@@ -274,7 +275,7 @@ export const ListView = ({
274
275
  delete params.page
275
276
  params.query = query
276
277
  navigate({
277
- to: '/admin/collections/$collection' as never,
278
+ to: getAdminRoutePath('collections', '$collection'),
278
279
  params: { collection: data.included.collection.path },
279
280
  search: params,
280
281
  })
@@ -286,7 +287,7 @@ export const ListView = ({
286
287
  delete params.page
287
288
  delete params.query
288
289
  navigate({
289
- to: '/admin/collections/$collection' as never,
290
+ to: getAdminRoutePath('collections', '$collection'),
290
291
  params: { collection: data.included.collection.path },
291
292
  search: params,
292
293
  })
@@ -302,7 +303,7 @@ export const ListView = ({
302
303
  params.status = value
303
304
  }
304
305
  navigate({
305
- to: '/admin/collections/$collection' as never,
306
+ to: getAdminRoutePath('collections', '$collection'),
306
307
  params: { collection: data.included.collection.path },
307
308
  search: params,
308
309
  })
@@ -314,7 +315,7 @@ export const ListView = ({
314
315
  delete params.page
315
316
  params.page_size = Number.parseInt(value, 10)
316
317
  navigate({
317
- to: '/admin/collections/$collection' as never,
318
+ to: getAdminRoutePath('collections', '$collection'),
318
319
  params: { collection: data.included.collection.path },
319
320
  search: params,
320
321
  })
@@ -336,7 +337,7 @@ export const ListView = ({
336
337
  aria-label={t('collections.list.createAriaLabel')}
337
338
  render={
338
339
  <Link
339
- to={'/admin/collections/$collection/create' as never}
340
+ to={getAdminRoutePath('collections', '$collection', 'create')}
340
341
  params={{ collection: data.included.collection.path }}
341
342
  />
342
343
  }
@@ -454,7 +455,7 @@ export const ListView = ({
454
455
  >
455
456
  {useAsTitle && column.fieldName === useAsTitle ? (
456
457
  <Link
457
- to={'/admin/collections/$collection/$id' as never}
458
+ to={getAdminRoutePath('collections', '$collection', '$id')}
458
459
  params={{
459
460
  collection: data.included.collection.path,
460
461
  id: document.id,
@@ -530,7 +531,7 @@ export const ListView = ({
530
531
  >
531
532
  {useAsTitle && column.fieldName === useAsTitle ? (
532
533
  <Link
533
- to={'/admin/collections/$collection/$id' as never}
534
+ to={getAdminRoutePath('collections', '$collection', '$id')}
534
535
  params={{
535
536
  collection: data.included.collection.path,
536
537
  id: document.id,
@@ -24,6 +24,7 @@ import { useTranslation } from '@byline/i18n/react'
24
24
  import { Alert, Button, LoaderEllipsis, Modal } from '@byline/ui/react'
25
25
  import cx from 'classnames'
26
26
 
27
+ import { getAdminRoutePath } from '../../routes/admin-path.js'
27
28
  import { restoreDocumentVersion } from '../../server-fns/collections/index.js'
28
29
  import { useNavigate } from '../chrome/loose-router.js'
29
30
  import styles from './restore-version-modal.module.css'
@@ -62,7 +63,7 @@ export function RestoreVersionModal({
62
63
  onClose()
63
64
  await router.invalidate()
64
65
  navigate({
65
- to: '/admin/collections/$collection/$id' as never,
66
+ to: getAdminRoutePath('collections', '$collection', '$id'),
66
67
  params: { collection, id: documentId },
67
68
  })
68
69
  } catch (err) {