@byline/host-tanstack-start 3.21.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) 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/i18n/resolve-locale.js +1 -1
  21. package/dist/index.d.ts +5 -6
  22. package/dist/integrations/abilities.js +8 -7
  23. package/dist/integrations/host-bridge.d.ts +13 -0
  24. package/dist/integrations/host-bridge.js +22 -0
  25. package/dist/integrations/serve-uploads.d.ts +1 -0
  26. package/dist/integrations/serve-uploads.js +1 -0
  27. package/dist/integrations/start-errors.js +2 -1
  28. package/dist/routes/admin-path.d.ts +22 -0
  29. package/dist/routes/admin-path.js +35 -0
  30. package/dist/routes/create-admin-account-route.js +3 -2
  31. package/dist/routes/create-admin-activity-route.js +3 -2
  32. package/dist/routes/create-admin-dashboard-route.js +2 -1
  33. package/dist/routes/create-admin-layout-route.d.ts +1 -1
  34. package/dist/routes/create-admin-layout-route.js +3 -1
  35. package/dist/routes/create-admin-permissions-route.js +3 -2
  36. package/dist/routes/create-admin-role-edit-route.js +4 -3
  37. package/dist/routes/create-admin-roles-list-route.js +3 -2
  38. package/dist/routes/create-admin-user-edit-route.js +4 -3
  39. package/dist/routes/create-admin-users-list-route.js +3 -2
  40. package/dist/routes/create-collection-api-route.js +5 -4
  41. package/dist/routes/create-collection-create-route.js +4 -3
  42. package/dist/routes/create-collection-edit-route.js +4 -3
  43. package/dist/routes/create-collection-history-route.js +5 -4
  44. package/dist/routes/create-collection-list-route.js +3 -2
  45. package/dist/routes/create-sign-in-route.js +3 -2
  46. package/dist/routes/index.d.ts +3 -0
  47. package/dist/routes/index.js +2 -0
  48. package/dist/routes/sign-in-path.d.ts +13 -0
  49. package/dist/routes/sign-in-path.js +19 -0
  50. package/dist/server-fns/admin-account/change-password.js +1 -1
  51. package/dist/server-fns/admin-account/get.js +1 -1
  52. package/dist/server-fns/admin-account/update.js +1 -1
  53. package/dist/server-fns/admin-activity/get.js +1 -1
  54. package/dist/server-fns/admin-permissions/get-role-abilities.js +1 -1
  55. package/dist/server-fns/admin-permissions/list-registered.js +1 -1
  56. package/dist/server-fns/admin-permissions/set-role-abilities.js +1 -1
  57. package/dist/server-fns/admin-permissions/who-has.js +1 -1
  58. package/dist/server-fns/admin-roles/create.js +1 -1
  59. package/dist/server-fns/admin-roles/delete.js +1 -1
  60. package/dist/server-fns/admin-roles/get.js +1 -1
  61. package/dist/server-fns/admin-roles/list.js +1 -1
  62. package/dist/server-fns/admin-roles/reorder.js +1 -1
  63. package/dist/server-fns/admin-roles/update.js +1 -1
  64. package/dist/server-fns/admin-users/create.js +1 -1
  65. package/dist/server-fns/admin-users/delete.js +1 -1
  66. package/dist/server-fns/admin-users/disable.js +1 -1
  67. package/dist/server-fns/admin-users/enable.js +1 -1
  68. package/dist/server-fns/admin-users/get-user-roles.js +1 -1
  69. package/dist/server-fns/admin-users/get.js +1 -1
  70. package/dist/server-fns/admin-users/list.js +1 -1
  71. package/dist/server-fns/admin-users/set-password.js +1 -1
  72. package/dist/server-fns/admin-users/set-user-roles.js +1 -1
  73. package/dist/server-fns/admin-users/update.js +1 -1
  74. package/dist/server-fns/ai/execute.js +1 -1
  75. package/dist/server-fns/auth/current-user.js +1 -1
  76. package/dist/server-fns/auth/sign-in.js +1 -1
  77. package/dist/server-fns/auth/sign-out.js +1 -2
  78. package/dist/server-fns/collections/audit.js +1 -1
  79. package/dist/server-fns/collections/copy-to-locale.js +1 -1
  80. package/dist/server-fns/collections/create.js +1 -1
  81. package/dist/server-fns/collections/delete-locale.js +1 -1
  82. package/dist/server-fns/collections/delete-outcome.d.ts +30 -0
  83. package/dist/server-fns/collections/delete-outcome.js +34 -0
  84. package/dist/server-fns/collections/delete.d.ts +1 -4
  85. package/dist/server-fns/collections/delete.js +3 -5
  86. package/dist/server-fns/collections/duplicate.js +1 -1
  87. package/dist/server-fns/collections/get.js +1 -1
  88. package/dist/server-fns/collections/history.js +1 -1
  89. package/dist/server-fns/collections/index.d.ts +1 -0
  90. package/dist/server-fns/collections/index.js +1 -0
  91. package/dist/server-fns/collections/list.js +1 -1
  92. package/dist/server-fns/collections/reindex.js +1 -1
  93. package/dist/server-fns/collections/reorder.js +1 -1
  94. package/dist/server-fns/collections/restore-version.js +1 -1
  95. package/dist/server-fns/collections/stats.js +1 -1
  96. package/dist/server-fns/collections/status.js +1 -1
  97. package/dist/server-fns/collections/tree-mutation.d.ts +46 -0
  98. package/dist/server-fns/collections/tree-mutation.js +109 -0
  99. package/dist/server-fns/collections/tree-read.d.ts +6 -0
  100. package/dist/server-fns/collections/tree-read.js +15 -0
  101. package/dist/server-fns/collections/tree.d.ts +5 -4
  102. package/dist/server-fns/collections/tree.js +16 -13
  103. package/dist/server-fns/collections/update.js +3 -2
  104. package/dist/server-fns/collections/upload.js +1 -1
  105. package/dist/server-fns/i18n/set-locale.js +1 -1
  106. package/dist/server-fns/preview/disable.js +1 -1
  107. package/dist/server-fns/preview/enable.js +1 -2
  108. package/dist/server-fns/preview/state.js +1 -1
  109. package/dist/vite/client-hook-boundary.d.ts +19 -0
  110. package/dist/vite/client-hook-boundary.js +20 -0
  111. package/dist/{integrations/byline-public-client.d.ts → vite/index.d.ts} +1 -2
  112. package/dist/vite/index.js +1 -0
  113. package/package.json +15 -14
  114. package/src/admin-shell/admin-activity/list.tsx +13 -3
  115. package/src/admin-shell/admin-roles/delete.tsx +2 -1
  116. package/src/admin-shell/admin-roles/list.tsx +2 -1
  117. package/src/admin-shell/admin-users/delete.tsx +3 -2
  118. package/src/admin-shell/admin-users/list.tsx +9 -8
  119. package/src/admin-shell/chrome/admin-app-bar.tsx +2 -1
  120. package/src/admin-shell/chrome/admin-menu-drawer.tsx +13 -8
  121. package/src/admin-shell/chrome/admin-menu-provider.tsx +2 -1
  122. package/src/admin-shell/chrome/dashboard.tsx +4 -3
  123. package/src/admin-shell/chrome/sign-in-page.tsx +3 -3
  124. package/src/admin-shell/collections/create.tsx +4 -3
  125. package/src/admin-shell/collections/document-history.tsx +5 -0
  126. package/src/admin-shell/collections/edit.tsx +20 -13
  127. package/src/admin-shell/collections/history.tsx +6 -5
  128. package/src/admin-shell/collections/list.tsx +8 -7
  129. package/src/admin-shell/collections/restore-version-modal.tsx +2 -1
  130. package/src/admin-shell/collections/tree-list.tsx +78 -22
  131. package/src/admin-shell/collections/view-menu.tsx +9 -8
  132. package/src/i18n/resolve-locale.ts +1 -1
  133. package/src/index.ts +5 -6
  134. package/src/integrations/abilities.tsx +14 -4
  135. package/src/integrations/host-bridge.ts +56 -0
  136. package/src/integrations/serve-uploads.ts +6 -0
  137. package/src/integrations/start-errors.test.node.ts +22 -0
  138. package/src/integrations/start-errors.ts +1 -0
  139. package/src/routes/admin-path.test.node.ts +129 -0
  140. package/src/routes/admin-path.ts +66 -0
  141. package/src/routes/auth-routes.test.node.ts +39 -0
  142. package/src/routes/create-admin-account-route.tsx +3 -2
  143. package/src/routes/create-admin-activity-route.tsx +3 -2
  144. package/src/routes/create-admin-dashboard-route.tsx +2 -1
  145. package/src/routes/create-admin-layout-route.tsx +4 -4
  146. package/src/routes/create-admin-permissions-route.tsx +3 -2
  147. package/src/routes/create-admin-role-edit-route.tsx +7 -3
  148. package/src/routes/create-admin-roles-list-route.tsx +3 -2
  149. package/src/routes/create-admin-user-edit-route.tsx +7 -3
  150. package/src/routes/create-admin-users-list-route.tsx +3 -2
  151. package/src/routes/create-collection-api-route.tsx +8 -4
  152. package/src/routes/create-collection-create-route.tsx +7 -3
  153. package/src/routes/create-collection-edit-route.tsx +7 -3
  154. package/src/routes/create-collection-history-route.tsx +8 -4
  155. package/src/routes/create-collection-list-route.tsx +3 -2
  156. package/src/routes/create-sign-in-route.tsx +9 -3
  157. package/src/routes/index.ts +11 -0
  158. package/src/routes/sign-in-path.test.node.ts +79 -0
  159. package/src/routes/sign-in-path.ts +36 -0
  160. package/src/server-fns/admin-account/change-password.ts +1 -1
  161. package/src/server-fns/admin-account/get.ts +1 -1
  162. package/src/server-fns/admin-account/update.ts +1 -1
  163. package/src/server-fns/admin-activity/get.ts +1 -1
  164. package/src/server-fns/admin-permissions/get-role-abilities.ts +1 -1
  165. package/src/server-fns/admin-permissions/list-registered.ts +1 -1
  166. package/src/server-fns/admin-permissions/set-role-abilities.ts +1 -1
  167. package/src/server-fns/admin-permissions/who-has.ts +1 -1
  168. package/src/server-fns/admin-roles/create.ts +1 -1
  169. package/src/server-fns/admin-roles/delete.ts +1 -1
  170. package/src/server-fns/admin-roles/get.ts +1 -1
  171. package/src/server-fns/admin-roles/list.ts +1 -1
  172. package/src/server-fns/admin-roles/reorder.ts +1 -1
  173. package/src/server-fns/admin-roles/update.ts +1 -1
  174. package/src/server-fns/admin-users/create.ts +1 -1
  175. package/src/server-fns/admin-users/delete.ts +1 -1
  176. package/src/server-fns/admin-users/disable.ts +1 -1
  177. package/src/server-fns/admin-users/enable.ts +1 -1
  178. package/src/server-fns/admin-users/get-user-roles.ts +1 -1
  179. package/src/server-fns/admin-users/get.ts +1 -1
  180. package/src/server-fns/admin-users/list.ts +1 -1
  181. package/src/server-fns/admin-users/set-password.ts +1 -1
  182. package/src/server-fns/admin-users/set-user-roles.ts +1 -1
  183. package/src/server-fns/admin-users/update.ts +1 -1
  184. package/src/server-fns/ai/execute.ts +1 -2
  185. package/src/server-fns/auth/current-user.ts +1 -1
  186. package/src/server-fns/auth/sign-in.ts +1 -1
  187. package/src/server-fns/auth/sign-out.ts +5 -3
  188. package/src/server-fns/collections/audit.ts +1 -1
  189. package/src/server-fns/collections/copy-to-locale.ts +1 -1
  190. package/src/server-fns/collections/create.ts +1 -1
  191. package/src/server-fns/collections/delete-locale.ts +1 -1
  192. package/src/server-fns/collections/delete-outcome.test.node.ts +72 -0
  193. package/src/server-fns/collections/delete-outcome.ts +80 -0
  194. package/src/server-fns/collections/delete.ts +3 -2
  195. package/src/server-fns/collections/duplicate.ts +1 -1
  196. package/src/server-fns/collections/get.ts +3 -1
  197. package/src/server-fns/collections/history.ts +1 -1
  198. package/src/server-fns/collections/index.ts +1 -0
  199. package/src/server-fns/collections/list.ts +1 -1
  200. package/src/server-fns/collections/reindex.ts +1 -1
  201. package/src/server-fns/collections/reorder.ts +1 -1
  202. package/src/server-fns/collections/restore-version.ts +1 -1
  203. package/src/server-fns/collections/stats.ts +2 -1
  204. package/src/server-fns/collections/status.ts +1 -1
  205. package/src/server-fns/collections/tree-mutation.test.node.ts +225 -0
  206. package/src/server-fns/collections/tree-mutation.ts +165 -0
  207. package/src/server-fns/collections/tree-read.test.node.ts +46 -0
  208. package/src/server-fns/collections/tree-read.ts +26 -0
  209. package/src/server-fns/collections/tree.ts +27 -11
  210. package/src/server-fns/collections/update.ts +4 -1
  211. package/src/server-fns/collections/upload.ts +1 -1
  212. package/src/server-fns/i18n/set-locale.ts +1 -1
  213. package/src/server-fns/preview/disable.ts +1 -1
  214. package/src/server-fns/preview/enable.ts +1 -2
  215. package/src/server-fns/preview/state.ts +1 -1
  216. package/src/vite/client-hook-boundary.test.node.ts +50 -0
  217. package/src/vite/client-hook-boundary.ts +45 -0
  218. package/src/vite/index.ts +9 -0
  219. package/dist/auth/auth-context.d.ts +0 -37
  220. package/dist/auth/auth-context.js +0 -57
  221. package/dist/auth/auth-cookies.d.ts +0 -34
  222. package/dist/auth/auth-cookies.js +0 -46
  223. package/dist/auth/preview-cookies.d.ts +0 -19
  224. package/dist/auth/preview-cookies.js +0 -26
  225. package/dist/integrations/byline-client.d.ts +0 -24
  226. package/dist/integrations/byline-client.js +0 -25
  227. package/dist/integrations/byline-public-client.js +0 -15
  228. package/dist/integrations/byline-viewer-client.d.ts +0 -20
  229. package/dist/integrations/byline-viewer-client.js +0 -39
  230. package/src/auth/auth-context.test.node.ts +0 -215
  231. package/src/auth/auth-context.ts +0 -124
  232. package/src/auth/auth-cookies.ts +0 -113
  233. package/src/auth/preview-cookies.ts +0 -73
  234. package/src/integrations/byline-client.ts +0 -66
  235. package/src/integrations/byline-public-client.ts +0 -53
  236. package/src/integrations/byline-viewer-client.ts +0 -99
@@ -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) {
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  import type React from 'react'
10
- import { useEffect, useMemo, useState } from 'react'
10
+ import { useEffect, useMemo, useRef, useState } from 'react'
11
11
  import { useRouter } from '@tanstack/react-router'
12
12
 
13
13
  import { renderFormatted, StatusBadge } from '@byline/admin/react'
@@ -41,6 +41,12 @@ import {
41
41
  } from '@dnd-kit/sortable'
42
42
  import cx from 'classnames'
43
43
 
44
+ import { getAdminRoutePath } from '../../routes/admin-path.js'
45
+ import {
46
+ createAdminTreeMoveController,
47
+ getAdminTreeMoveUiEffects,
48
+ shouldSyncAdminTreeRows,
49
+ } from '../../server-fns/collections/tree-mutation.js'
44
50
  import { Link } from '../chrome/loose-router.js'
45
51
  import { TableHeadingCellSortable } from '../chrome/th-sortable.js'
46
52
  import { formatNumber } from '../chrome/utils.js'
@@ -59,7 +65,6 @@ export type TreeMoveFn = (params: {
59
65
  afterDocumentId: string | null
60
66
  }) => Promise<unknown>
61
67
 
62
- // biome-ignore lint/suspicious/noExplicitAny: tree rows carry heterogeneous fields
63
68
  function getColumnValue(row: CollectionTreeRow, fieldName: string): any {
64
69
  if (row.fields && fieldName in row.fields) return row.fields[fieldName]
65
70
  return (row as Record<string, any>)[fieldName]
@@ -73,19 +78,20 @@ function getColumnValue(row: CollectionTreeRow, fieldName: string): any {
73
78
  */
74
79
  function SortableTreeRow({
75
80
  id,
76
- depth,
77
81
  dragging,
82
+ disabled,
78
83
  dragHandleLabel,
79
84
  children,
80
85
  }: {
81
86
  id: string
82
- depth: number
83
87
  dragging: boolean
88
+ disabled: boolean
84
89
  dragHandleLabel: string
85
90
  children: React.ReactNode
86
91
  }) {
87
92
  const { setNodeRef, attributes, listeners, transform, transition, isDragging } = useSortable({
88
93
  id,
94
+ disabled,
89
95
  })
90
96
  const style: React.CSSProperties = {
91
97
  transform: transform ? `translate3d(0, ${transform.y}px, 0)` : undefined,
@@ -103,6 +109,7 @@ function SortableTreeRow({
103
109
  <td className={cx('byline-tree-list-drag-cell', styles.dragCell)}>
104
110
  <button
105
111
  type="button"
112
+ disabled={disabled}
106
113
  className={cx('byline-tree-list-drag-handle', styles.dragHandle)}
107
114
  aria-label={dragHandleLabel}
108
115
  {...attributes}
@@ -149,8 +156,12 @@ export const TreeListView = ({
149
156
  // back). Mirrors the flat-list reorder pattern.
150
157
  const [localPlaced, setLocalPlaced] = useState(() => rows.filter((r) => !r.unplaced))
151
158
  const [isMoving, setIsMoving] = useState(false)
159
+ const [moveController] = useState(() => createAdminTreeMoveController())
160
+ const preserveOptimisticForRows = useRef<CollectionTreeRow[] | null>(null)
152
161
  useEffect(() => {
153
- if (!isMoving) setLocalPlaced(rows.filter((r) => !r.unplaced))
162
+ if (!shouldSyncAdminTreeRows(rows, preserveOptimisticForRows.current, isMoving)) return
163
+ preserveOptimisticForRows.current = null
164
+ setLocalPlaced(rows.filter((r) => !r.unplaced))
154
165
  }, [rows, isMoving])
155
166
 
156
167
  const [activeId, setActiveId] = useState<string | null>(null)
@@ -174,11 +185,17 @@ export const TreeListView = ({
174
185
  setOffsetLeft(0)
175
186
  }
176
187
 
177
- const handleDragStart = ({ active }: DragStartEvent) => setActiveId(String(active.id))
188
+ const handleDragStart = ({ active }: DragStartEvent) => {
189
+ if (!moveController.isMoving()) setActiveId(String(active.id))
190
+ }
178
191
  const handleDragMove = ({ delta }: DragMoveEvent) => setOffsetLeft(delta.x)
179
192
  const handleDragOver = ({ over }: DragOverEvent) => setOverId(over ? String(over.id) : null)
180
193
 
181
194
  const handleDragEnd = async ({ active, over }: DragEndEvent) => {
195
+ if (moveController.isMoving()) {
196
+ resetDnd()
197
+ return
198
+ }
182
199
  const proj =
183
200
  over != null
184
201
  ? getTreeProjection(localPlaced, String(active.id), String(over.id), offsetLeft, INDENT)
@@ -197,24 +214,60 @@ export const TreeListView = ({
197
214
 
198
215
  setLocalPlaced(next)
199
216
  setIsMoving(true)
200
- try {
201
- await onMove({
202
- documentId,
203
- parentDocumentId: proj.parentId,
204
- beforeDocumentId: proj.beforeId,
205
- afterDocumentId: proj.afterId,
206
- })
207
- await router.invalidate()
208
- } catch {
217
+ const outcome = await moveController.execute(
218
+ () =>
219
+ onMove({
220
+ documentId,
221
+ parentDocumentId: proj.parentId,
222
+ beforeDocumentId: proj.beforeId,
223
+ afterDocumentId: proj.afterId,
224
+ }),
225
+ () => router.invalidate()
226
+ )
227
+
228
+ if (outcome.status === 'suppressed') {
209
229
  setLocalPlaced(current)
230
+ setIsMoving(moveController.isMoving())
231
+ return
232
+ }
233
+
234
+ const effects = getAdminTreeMoveUiEffects(outcome)
235
+ if (effects.rollback) setLocalPlaced(current)
236
+
237
+ if (effects.structuralFailure != null) {
238
+ const conflict = effects.structuralFailure === 'conflict'
210
239
  toastManager.add({
211
- title: t('collections.list.reorderFailedToast'),
212
- description: t('collections.list.reorderFailedDescription'),
240
+ title: t(
241
+ conflict ? 'collections.list.treeConflictToast' : 'collections.list.reorderFailedToast'
242
+ ),
243
+ description: t(
244
+ conflict
245
+ ? 'collections.list.treeConflictDescription'
246
+ : 'collections.list.reorderFailedDescription'
247
+ ),
213
248
  data: { intent: 'danger', iconType: 'danger', icon: true, close: true },
214
249
  })
215
- } finally {
216
- setIsMoving(false)
217
250
  }
251
+
252
+ if (effects.committedHookWarning) {
253
+ toastManager.add({
254
+ title: t('collections.list.treeHookFailedToast'),
255
+ description: t('collections.list.treeHookFailedDescription'),
256
+ data: { intent: 'warning', iconType: 'warning', icon: true, close: true },
257
+ })
258
+ }
259
+
260
+ if (effects.refreshWarning) {
261
+ toastManager.add({
262
+ title: t('collections.list.treeRefreshFailedToast'),
263
+ description: t('collections.list.treeRefreshFailedDescription'),
264
+ data: { intent: 'warning', iconType: 'warning', icon: true, close: true },
265
+ })
266
+ }
267
+
268
+ if (effects.preserveOptimistic) preserveOptimisticForRows.current = rows
269
+
270
+ setIsMoving(false)
218
271
  }
219
272
 
220
273
  const renderCells = (row: CollectionTreeRow, depth: number) =>
@@ -240,7 +293,7 @@ export const TreeListView = ({
240
293
  </span>
241
294
  )}
242
295
  <Link
243
- to={'/admin/collections/$collection/$id' as never}
296
+ to={getAdminRoutePath('collections', '$collection', '$id')}
244
297
  params={{ collection, id: row.id }}
245
298
  >
246
299
  {column.formatter
@@ -277,7 +330,10 @@ export const TreeListView = ({
277
330
  <IconButton
278
331
  aria-label={t('collections.list.createAriaLabel')}
279
332
  render={
280
- <Link to={'/admin/collections/$collection/create' as never} params={{ collection }} />
333
+ <Link
334
+ to={getAdminRoutePath('collections', '$collection', 'create')}
335
+ params={{ collection }}
336
+ />
281
337
  }
282
338
  >
283
339
  <PlusIcon height="18px" width="18px" svgClassName="stroke-white" />
@@ -323,8 +379,8 @@ export const TreeListView = ({
323
379
  <SortableTreeRow
324
380
  key={row.id}
325
381
  id={row.id}
326
- depth={depth}
327
382
  dragging={row.id === activeId}
383
+ disabled={isMoving}
328
384
  dragHandleLabel={t('collections.list.dragHandleAriaLabel')}
329
385
  >
330
386
  {renderCells(row, depth)}
@@ -13,6 +13,7 @@ import { useTranslation } from '@byline/i18n/react'
13
13
  import { Button, HistoryIcon, IconButton, Label, Select } from '@byline/ui/react'
14
14
  import cx from 'classnames'
15
15
 
16
+ import { getAdminRoutePath } from '../../routes/admin-path.js'
16
17
  import { useNavigate } from '../chrome/loose-router.js'
17
18
  import { PreviewLink } from './preview-link.js'
18
19
  import styles from './view-menu.module.css'
@@ -86,7 +87,7 @@ export const ViewMenu = ({
86
87
  useEffect(() => {
87
88
  if (activeView === 'edit' && locale === 'all') {
88
89
  navigate({
89
- to: '/admin/collections/$collection/$id' as never,
90
+ to: getAdminRoutePath('collections', '$collection', '$id'),
90
91
  params: { collection, id: documentId },
91
92
  search: (prev: Record<string, unknown>) => ({
92
93
  ...prev,
@@ -103,19 +104,19 @@ export const ViewMenu = ({
103
104
  const search = (prev: Record<string, unknown>) => ({ ...prev, locale: value })
104
105
  if (activeView === 'api') {
105
106
  navigate({
106
- to: '/admin/collections/$collection/$id/api' as never,
107
+ to: getAdminRoutePath('collections', '$collection', '$id', 'api'),
107
108
  params: { collection, id: documentId },
108
109
  search: search as never,
109
110
  })
110
111
  } else if (activeView === 'history') {
111
112
  navigate({
112
- to: '/admin/collections/$collection/$id/history' as never,
113
+ to: getAdminRoutePath('collections', '$collection', '$id', 'history'),
113
114
  params: { collection, id: documentId },
114
115
  search: search as never,
115
116
  })
116
117
  } else {
117
118
  navigate({
118
- to: '/admin/collections/$collection/$id' as never,
119
+ to: getAdminRoutePath('collections', '$collection', '$id'),
119
120
  params: { collection, id: documentId },
120
121
  search: search as never,
121
122
  })
@@ -127,7 +128,7 @@ export const ViewMenu = ({
127
128
  const n = Number.parseInt(value, 10)
128
129
  const nextDepth = Number.isFinite(n) ? Math.max(0, Math.min(3, n)) : 0
129
130
  navigate({
130
- to: '/admin/collections/$collection/$id/api' as never,
131
+ to: getAdminRoutePath('collections', '$collection', '$id', 'api'),
131
132
  params: { collection, id: documentId },
132
133
  // Store 0 as undefined so the URL stays clean when the user picks
133
134
  // "no populate" — avoids `?depth=0` in bookmarks / share links.
@@ -201,7 +202,7 @@ export const ViewMenu = ({
201
202
  variant={activeView === 'history' ? 'filled' : 'text'}
202
203
  onClick={() =>
203
204
  navigate({
204
- to: '/admin/collections/$collection/$id/history' as never,
205
+ to: getAdminRoutePath('collections', '$collection', '$id', 'history'),
205
206
  params: { collection, id: documentId },
206
207
  search: locale ? { locale } : {},
207
208
  })
@@ -215,7 +216,7 @@ export const ViewMenu = ({
215
216
  className={cx('byline-view-menu-button', styles.button)}
216
217
  onClick={() =>
217
218
  navigate({
218
- to: '/admin/collections/$collection/$id' as never,
219
+ to: getAdminRoutePath('collections', '$collection', '$id'),
219
220
  params: { collection, id: documentId },
220
221
  search: locale ? { locale } : {},
221
222
  })
@@ -229,7 +230,7 @@ export const ViewMenu = ({
229
230
  className={cx('byline-view-menu-button', styles.button)}
230
231
  onClick={() =>
231
232
  navigate({
232
- to: '/admin/collections/$collection/$id/api' as never,
233
+ to: getAdminRoutePath('collections', '$collection', '$id', 'api'),
233
234
  params: { collection, id: documentId },
234
235
  search: locale ? { locale } : {},
235
236
  })
@@ -23,10 +23,10 @@
23
23
  import { getRequestHeader } from '@tanstack/react-start/server'
24
24
 
25
25
  import { AuthError, AuthErrorCodes } from '@byline/auth'
26
+ import { getAdminRequestContext } from '@byline/client/server'
26
27
  import type { LocaleCode } from '@byline/i18n'
27
28
  import { resolveInterfaceLocale } from '@byline/i18n'
28
29
 
29
- import { getAdminRequestContext } from '../auth/auth-context.js'
30
30
  import { bylineCore } from '../integrations/byline-core.js'
31
31
  import { readAdminLocaleCookie } from './locale-cookie.js'
32
32
 
package/src/index.ts CHANGED
@@ -19,14 +19,13 @@
19
19
  * - `@byline/host-tanstack-start/server-fns/<module>` — TanStack Start
20
20
  * server functions for each admin module (admin-account, admin-roles,
21
21
  * admin-users, admin-permissions, auth, collections).
22
- * - `@byline/host-tanstack-start/auth/auth-context` — request-scoped
23
- * `RequestContext` resolution that reads session cookies, refreshes
24
- * transparently, and surfaces `ERR_UNAUTHENTICATED` to callers.
25
22
  * - `@byline/host-tanstack-start/integrations/*` — host-side adapters
26
23
  * binding TanStack Start primitives to the framework-neutral
27
- * contracts in `@byline/ui` and `@byline/client`
28
- * (`bylineFieldServices`, `bylineAdminServices`, the admin
29
- * `BylineClient` singleton).
24
+ * contracts in `@byline/ui`, `@byline/core`, and `@byline/client`
25
+ * (`bylineFieldServices`, `bylineAdminServices`, and `host-bridge` —
26
+ * the `HostRequestBridge` implementation behind the
27
+ * `@byline/client/server` getters; request-context resolution and the
28
+ * client singletons themselves live in `@byline/client/server`).
30
29
  * - `@byline/host-tanstack-start/admin-shell/{chrome,collections,...}` —
31
30
  * router-coupled admin UI: shared shell chrome (menu drawer, app bar,
32
31
  * route-error, breadcrumbs, etc.) plus per-area page containers.
@@ -38,15 +38,25 @@
38
38
  import type React from 'react'
39
39
  import { useRouteContext } from '@tanstack/react-router'
40
40
 
41
- /** Route id of the admin shell — the layout route that owns `user` on context. */
42
- const ADMIN_ROUTE_ID = '/_byline/admin' as const
41
+ import { getAdminRouteId } from '../routes/admin-path.js'
42
+
43
+ interface AbilityRouteContext {
44
+ user: {
45
+ is_super_admin: boolean
46
+ abilities: string[]
47
+ }
48
+ }
49
+
50
+ function useAbilityRouteContext(): AbilityRouteContext {
51
+ return useRouteContext({ from: getAdminRouteId() as never }) as AbilityRouteContext
52
+ }
43
53
 
44
54
  /**
45
55
  * `true` when the current admin holds the given ability (or is a
46
56
  * super-admin). Cosmetic — see file-level docstring.
47
57
  */
48
58
  export function useAbility(ability: string): boolean {
49
- const { user } = useRouteContext({ from: ADMIN_ROUTE_ID })
59
+ const { user } = useAbilityRouteContext()
50
60
  if (user.is_super_admin) return true
51
61
  return user.abilities.includes(ability)
52
62
  }
@@ -62,7 +72,7 @@ export function useAbilities(): {
62
72
  hasAny: (abilities: readonly string[]) => boolean
63
73
  isSuperAdmin: boolean
64
74
  } {
65
- const { user } = useRouteContext({ from: ADMIN_ROUTE_ID })
75
+ const { user } = useAbilityRouteContext()
66
76
  const has = (ability: string): boolean => user.is_super_admin || user.abilities.includes(ability)
67
77
  const hasAny = (abilities: readonly string[]): boolean =>
68
78
  user.is_super_admin || abilities.some((key) => user.abilities.includes(key))
@@ -0,0 +1,56 @@
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
+ /**
10
+ * The TanStack Start implementation of `HostRequestBridge` — the three
11
+ * request primitives (`getRequest` identity, cookie read, cookie write)
12
+ * that `@byline/client/server` is written against.
13
+ *
14
+ * Registered as a module side effect: every module in this package that
15
+ * fronts the server-side client stack imports this file first, so any
16
+ * code path that can reach a getter has already registered the bridge.
17
+ * Registration is idempotent (last-write-wins on a `globalThis` symbol
18
+ * slot), so repeated evaluation across Vite SSR module graphs is
19
+ * harmless.
20
+ */
21
+
22
+ import { getCookie, getRequest, setCookie } from '@tanstack/react-start/server'
23
+
24
+ import {
25
+ type HostCookieSetOptions,
26
+ type HostRequestBridge,
27
+ registerHostRequestBridge,
28
+ } from '@byline/core'
29
+
30
+ const tanstackStartRequestBridge: HostRequestBridge = {
31
+ getRequest(): object | undefined {
32
+ try {
33
+ return getRequest()
34
+ } catch {
35
+ // No StartEvent in AsyncLocalStorage — running outside a request.
36
+ return undefined
37
+ }
38
+ },
39
+ getCookie(name: string): string | undefined {
40
+ return getCookie(name)
41
+ },
42
+ setCookie(name: string, value: string, options?: HostCookieSetOptions): void {
43
+ setCookie(name, value, options)
44
+ },
45
+ }
46
+
47
+ /**
48
+ * Register the TanStack Start bridge. Safe to call any number of times;
49
+ * exported for hosts that want registration to be explicit in their
50
+ * server config rather than relying on a side-effect import.
51
+ */
52
+ export function registerTanstackStartHostBridge(): void {
53
+ registerHostRequestBridge(tanstackStartRequestBridge)
54
+ }
55
+
56
+ registerTanstackStartHostBridge()
@@ -46,6 +46,12 @@ import type { Stats } from 'node:fs'
46
46
  import { readFile, stat } from 'node:fs/promises'
47
47
  import { resolve as resolvePath } from 'node:path'
48
48
 
49
+ // Belt-and-braces bridge registration: `serveUploads` is imported by every
50
+ // scaffolded app's `src/server.ts` at boot, so pulling in the host bridge
51
+ // here guarantees `@byline/client/server` getters can resolve cookies even
52
+ // if a server config forgets the explicit `registerTanstackStartHostBridge()`.
53
+ import './host-bridge.js'
54
+
49
55
  export interface UploadsHandlerOptions {
50
56
  /**
51
57
  * URL prefix to match. Trailing slash required. Default `'/uploads/'`.
@@ -0,0 +1,22 @@
1
+ import { ERR_CONFLICT, ERR_TREE_HOOK_COMMITTED } from '@byline/core'
2
+ import { describe, expect, it } from 'vitest'
3
+
4
+ import { bylineCodedErrorAdapter } from './start-errors.js'
5
+
6
+ describe('Byline coded error serialization', () => {
7
+ it.each([
8
+ ERR_CONFLICT({ message: 'stale tree' }),
9
+ ERR_TREE_HOOK_COMMITTED({ message: 'tree committed; hook failed' }),
10
+ ])('preserves core BylineError code $code', (error) => {
11
+ expect(bylineCodedErrorAdapter.test(error)).toBe(true)
12
+
13
+ const payload = bylineCodedErrorAdapter.toSerializable(error)
14
+ const restored = bylineCodedErrorAdapter.fromSerializable(payload)
15
+
16
+ expect(restored).toMatchObject({
17
+ name: 'BylineError',
18
+ code: error.code,
19
+ message: error.message,
20
+ })
21
+ })
22
+ })
@@ -44,6 +44,7 @@ const CODED_ERROR_NAMES = new Set([
44
44
  'AdminRolesError',
45
45
  'AdminUsersError',
46
46
  'AuthError',
47
+ 'BylineError',
47
48
  ])
48
49
 
49
50
  export interface BylineCodedErrorPayload {