@byline/host-tanstack-start 6.8.0 → 6.10.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 (32) hide show
  1. package/dist/admin-shell/chrome/admin-app-bar.d.ts +0 -7
  2. package/dist/admin-shell/collections/api.d.ts +6 -2
  3. package/dist/admin-shell/collections/api.js +109 -3
  4. package/dist/admin-shell/collections/api.test.d.ts +8 -0
  5. package/dist/admin-shell/collections/edit.js +2 -0
  6. package/dist/admin-shell/collections/preview-link.js +17 -13
  7. package/dist/admin-shell/collections/view-menu.d.ts +8 -1
  8. package/dist/admin-shell/collections/view-menu.js +124 -107
  9. package/dist/admin-shell/collections/view-menu.module.js +1 -0
  10. package/dist/admin-shell/collections/view-menu_module.css +15 -1
  11. package/dist/admin-shell/scheduled-publications/list.js +2 -2
  12. package/dist/admin-shell/singletons/preview-link.js +17 -13
  13. package/dist/admin-shell/singletons/view-menu.js +78 -63
  14. package/dist/admin-shell/singletons/view.js +2 -0
  15. package/dist/server-fns/collections/get.d.ts +1 -0
  16. package/dist/server-fns/collections/get.js +4 -0
  17. package/package.json +15 -15
  18. package/src/admin-shell/chrome/admin-app-bar.tsx +2 -2
  19. package/src/admin-shell/chrome/dashboard.test.tsx +2 -2
  20. package/src/admin-shell/collections/api.test.tsx +223 -0
  21. package/src/admin-shell/collections/api.tsx +98 -3
  22. package/src/admin-shell/collections/edit.tsx +9 -1
  23. package/src/admin-shell/collections/preview-link.tsx +13 -12
  24. package/src/admin-shell/collections/view-menu.module.css +29 -3
  25. package/src/admin-shell/collections/view-menu.tsx +114 -96
  26. package/src/admin-shell/scheduled-publications/list.tsx +3 -4
  27. package/src/admin-shell/singletons/history.test.tsx +1 -1
  28. package/src/admin-shell/singletons/preview-link.tsx +13 -12
  29. package/src/admin-shell/singletons/view-menu.tsx +65 -59
  30. package/src/admin-shell/singletons/view.test.tsx +4 -4
  31. package/src/admin-shell/singletons/view.tsx +3 -1
  32. package/src/server-fns/collections/get.ts +3 -0
@@ -9,7 +9,11 @@
9
9
  import { useState } from 'react'
10
10
 
11
11
  import { FormRenderer } from '@byline/admin/react'
12
- import type { CollectionAdminConfig, MultiCollectionDefinition } from '@byline/core'
12
+ import type {
13
+ CollectionAdminConfig,
14
+ MultiCollectionDefinition,
15
+ UnavailableBlock,
16
+ } from '@byline/core'
13
17
  import {
14
18
  getDefaultStatus,
15
19
  getWorkflow,
@@ -321,6 +325,9 @@ export const EditView = ({
321
325
  // orphan row was skipped because the collection schema has moved on
322
326
  // since the document was written.
323
327
  // biome-ignore lint/suspicious/noExplicitAny: storage shape
328
+ const unavailableBlocks = (initialData as any)?._unavailableBlocks as
329
+ | UnavailableBlock[]
330
+ | undefined
324
331
  const restoreWarnings = (initialData as any)?._restoreWarnings as string[] | undefined
325
332
 
326
333
  const handleUnpublish = async () => {
@@ -854,6 +861,7 @@ export const EditView = ({
854
861
  contentLocales={contentLocales}
855
862
  publishedVersion={publishedVersion}
856
863
  restoreWarnings={restoreWarnings}
864
+ unavailableBlocks={unavailableBlocks}
857
865
  nextStatus={nextStatus}
858
866
  workflowStatuses={workflowStatuses}
859
867
  onCancel={() => {
@@ -47,7 +47,7 @@ import { useState } from 'react'
47
47
 
48
48
  import type { CollectionAdminConfig, PreviewDocument } from '@byline/core'
49
49
  import { useTranslation } from '@byline/i18n/react'
50
- import { ExternalLinkIcon, IconButton, useToastManager } from '@byline/ui/react'
50
+ import { ExternalLinkIcon, IconButton, Tooltip, useToastManager } from '@byline/ui/react'
51
51
  import cx from 'clsx'
52
52
 
53
53
  import { enablePreviewModeFn } from '../../server-fns/preview/index.js'
@@ -110,16 +110,17 @@ export const PreviewLink = ({
110
110
  }
111
111
 
112
112
  return (
113
- <IconButton
114
- className={cx('byline-preview-link', className)}
115
- size="xs"
116
- variant="text"
117
- disabled={busy}
118
- onClick={handleClick}
119
- aria-label={t('collections.preview.openAriaLabel')}
120
- title={t('collections.preview.title')}
121
- >
122
- <ExternalLinkIcon width="20px" height="20px" className="byline-preview-link-icon" />
123
- </IconButton>
113
+ <Tooltip text={t('collections.preview.title')} side="bottom" delay={0}>
114
+ <IconButton
115
+ className={cx('byline-preview-link', className)}
116
+ size="xs"
117
+ variant="text"
118
+ disabled={busy}
119
+ onClick={handleClick}
120
+ aria-label={t('collections.preview.openAriaLabel')}
121
+ >
122
+ <ExternalLinkIcon width="20px" height="20px" className="byline-preview-link-icon" />
123
+ </IconButton>
124
+ </Tooltip>
124
125
  )
125
126
  }
@@ -2,21 +2,47 @@
2
2
  * ViewMenu — mini-nav bar for document-level views (Edit, History, API).
3
3
  *
4
4
  * Override handles:
5
- * .byline-view-menu — outer flex row
5
+ * .byline-view-menu — outer row; wraps between its two groups
6
+ * .byline-view-menu-data — locale/depth selects and the data actions that
7
+ * act on them (API view: Copy / Download JSON)
8
+ * .byline-view-menu-nav — preview link and view navigation
6
9
  * .byline-view-menu-label — locale/depth label (lg-only)
7
10
  * .byline-view-menu-button — primary view buttons (Edit/API)
8
- * .byline-view-menu-icon-button — history icon button
11
+ * .byline-view-menu-icon-button — preview, history, and data-action icon buttons
9
12
  */
10
13
 
14
+ /* Groups wrap as units, so a narrow viewport breaks the row between "what
15
+ * you are looking at" and "where you can go" rather than mid-group. The
16
+ * wider column gap marks the boundary when both groups share a line. */
11
17
  .root,
12
18
  :global(.byline-view-menu) {
13
19
  display: flex;
20
+ flex-wrap: wrap;
14
21
  align-items: center;
15
- gap: 0.5rem;
22
+ gap: 0.5rem 1.25rem;
16
23
  margin-top: 0.5rem;
17
24
  margin-bottom: 0.5rem;
18
25
  }
19
26
 
27
+ /* Beside the heading (every host switches to a row at 40rem), keep a wrapped
28
+ * second line flush with the right edge rather than stranded under the
29
+ * heading's side of the menu. */
30
+ @media (min-width: 40rem) {
31
+ .root,
32
+ :global(.byline-view-menu) {
33
+ justify-content: flex-end;
34
+ }
35
+ }
36
+
37
+ .group,
38
+ :global(.byline-view-menu-data),
39
+ :global(.byline-view-menu-nav) {
40
+ display: flex;
41
+ flex-wrap: wrap;
42
+ align-items: center;
43
+ gap: 0.5rem;
44
+ }
45
+
20
46
  .label,
21
47
  :global(.byline-view-menu-label) {
22
48
  display: none;
@@ -6,11 +6,12 @@
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
8
 
9
+ import type { ReactNode } from 'react'
9
10
  import { useEffect } from 'react'
10
11
 
11
12
  import type { CollectionAdminConfig, PreviewDocument } from '@byline/core'
12
13
  import { useTranslation } from '@byline/i18n/react'
13
- import { Button, HistoryIcon, IconButton, Label, Select } from '@byline/ui/react'
14
+ import { Button, HistoryIcon, IconButton, Label, Select, Tooltip } from '@byline/ui/react'
14
15
  import cx from 'clsx'
15
16
 
16
17
  import { getAdminRoutePath } from '../../routes/admin-path.js'
@@ -51,6 +52,7 @@ export const ViewMenu = ({
51
52
  defaultContentLocale,
52
53
  adminConfig,
53
54
  doc,
55
+ dataActions,
54
56
  }: {
55
57
  /** Collection path (e.g. "docs", "news"). */
56
58
  collection: string
@@ -78,6 +80,12 @@ export const ViewMenu = ({
78
80
  * entirely (e.g. on the create route, where no doc exists yet).
79
81
  */
80
82
  doc?: PreviewDocument
83
+ /**
84
+ * Controls that act on the data selected by the locale and depth controls
85
+ * (the API view's Copy / Download JSON). Rendered first, in the same group
86
+ * as those controls, so they wrap together ahead of the view navigation.
87
+ */
88
+ dataActions?: ReactNode
81
89
  }) => {
82
90
  const navigate = useNavigate()
83
91
  const { t } = useTranslation('byline-admin')
@@ -141,103 +149,113 @@ export const ViewMenu = ({
141
149
 
142
150
  return (
143
151
  <div className={cx('byline-view-menu', styles.root)}>
144
- <Label
145
- className={cx('muted byline-view-menu-label', styles.label)}
146
- id="contentLocaleLabel"
147
- htmlFor="contentLocale"
148
- label={t('collections.viewMenu.contentLocaleLabel')}
149
- />
150
- <Select<string>
151
- name="contentLocale"
152
- id="contentLocale"
153
- className={cx('byline-view-menu-locale-select', styles.localeSelect)}
154
- size="xs"
155
- variant="outlined"
156
- value={locale ?? defaultContentLocale}
157
- items={[
158
- ...(activeView !== 'edit'
159
- ? [{ value: 'all', label: t('collections.viewMenu.localeAll') }]
160
- : []),
161
- ...contentLocales.map((loc) => ({ value: loc.code, label: loc.label })),
162
- ]}
163
- onValueChange={handleLocaleChange}
164
- />
165
- {activeView === 'api' && (
166
- <>
167
- <Label
168
- className={cx('muted byline-view-menu-label', styles.label)}
169
- id="populateDepthLabel"
170
- htmlFor="populateDepth"
171
- label={t('collections.viewMenu.depthLabel')}
152
+ <div className={cx('byline-view-menu-data', styles.group)}>
153
+ {dataActions}
154
+ <Label
155
+ className={cx('muted byline-view-menu-label', styles.label)}
156
+ id="contentLocaleLabel"
157
+ htmlFor="contentLocale"
158
+ label={t('collections.viewMenu.contentLocaleLabel')}
159
+ />
160
+ <Select<string>
161
+ name="contentLocale"
162
+ id="contentLocale"
163
+ className={cx('byline-view-menu-locale-select', styles.localeSelect)}
164
+ size="xs"
165
+ variant="outlined"
166
+ value={locale ?? defaultContentLocale}
167
+ items={[
168
+ ...(activeView !== 'edit'
169
+ ? [{ value: 'all', label: t('collections.viewMenu.localeAll') }]
170
+ : []),
171
+ ...contentLocales.map((loc) => ({ value: loc.code, label: loc.label })),
172
+ ]}
173
+ onValueChange={handleLocaleChange}
174
+ />
175
+ {activeView === 'api' && (
176
+ <>
177
+ <Label
178
+ className={cx('muted byline-view-menu-label', styles.label)}
179
+ id="populateDepthLabel"
180
+ htmlFor="populateDepth"
181
+ label={t('collections.viewMenu.depthLabel')}
182
+ />
183
+ <Select<string>
184
+ name="populateDepth"
185
+ id="populateDepth"
186
+ className={cx('byline-view-menu-depth-select', styles.depthSelect)}
187
+ size="xs"
188
+ variant="outlined"
189
+ value={String(depth ?? 0)}
190
+ items={[
191
+ { value: '0', label: '0' },
192
+ { value: '1', label: '1' },
193
+ { value: '2', label: '2' },
194
+ { value: '3', label: '3' },
195
+ ]}
196
+ onValueChange={handleDepthChange}
197
+ />
198
+ </>
199
+ )}
200
+ </div>
201
+ <div className={cx('byline-view-menu-nav', styles.group)}>
202
+ {doc && (
203
+ <PreviewLink
204
+ collectionPath={collection}
205
+ doc={doc}
206
+ adminConfig={adminConfig}
207
+ locale={locale}
208
+ className={cx('byline-view-menu-icon-button', styles.iconButton)}
172
209
  />
173
- <Select<string>
174
- name="populateDepth"
175
- id="populateDepth"
176
- className={cx('byline-view-menu-depth-select', styles.depthSelect)}
210
+ )}
211
+ <Tooltip text={t('collections.breadcrumbs.history')} side="bottom" delay={0}>
212
+ <IconButton
213
+ className={cx('byline-view-menu-icon-button', styles.iconButton)}
177
214
  size="xs"
178
- variant="outlined"
179
- value={String(depth ?? 0)}
180
- items={[
181
- { value: '0', label: '0' },
182
- { value: '1', label: '1' },
183
- { value: '2', label: '2' },
184
- { value: '3', label: '3' },
185
- ]}
186
- onValueChange={handleDepthChange}
187
- />
188
- </>
189
- )}
190
- {doc && (
191
- <PreviewLink
192
- collectionPath={collection}
193
- doc={doc}
194
- adminConfig={adminConfig}
195
- locale={locale}
196
- className={cx('byline-view-menu-icon-button', styles.iconButton)}
197
- />
198
- )}
199
- <IconButton
200
- className={cx('byline-view-menu-icon-button', styles.iconButton)}
201
- size="xs"
202
- variant={activeView === 'history' ? 'filled' : 'text'}
203
- onClick={() =>
204
- navigate({
205
- to: getAdminRoutePath('collections', '$collection', '$id', 'history'),
206
- params: { collection, id: documentId },
207
- search: locale ? { locale } : {},
208
- })
209
- }
210
- >
211
- <HistoryIcon className={cx('byline-view-menu-icon-button-icon', styles.iconButtonIcon)} />
212
- </IconButton>
213
- <Button
214
- size="xs"
215
- variant={activeView === 'edit' ? 'filled' : 'outlined'}
216
- className={cx('byline-view-menu-button', styles.button)}
217
- onClick={() =>
218
- navigate({
219
- to: getAdminRoutePath('collections', '$collection', '$id'),
220
- params: { collection, id: documentId },
221
- search: locale ? { locale } : {},
222
- })
223
- }
224
- >
225
- {t('common.actions.edit')}
226
- </Button>
227
- <Button
228
- size="xs"
229
- variant={activeView === 'api' ? 'filled' : 'outlined'}
230
- className={cx('byline-view-menu-button', styles.button)}
231
- onClick={() =>
232
- navigate({
233
- to: getAdminRoutePath('collections', '$collection', '$id', 'api'),
234
- params: { collection, id: documentId },
235
- search: locale ? { locale } : {},
236
- })
237
- }
238
- >
239
- {t('collections.viewMenu.apiButton')}
240
- </Button>
215
+ variant={activeView === 'history' ? 'filled' : 'text'}
216
+ aria-label={t('collections.breadcrumbs.history')}
217
+ onClick={() =>
218
+ navigate({
219
+ to: getAdminRoutePath('collections', '$collection', '$id', 'history'),
220
+ params: { collection, id: documentId },
221
+ search: locale ? { locale } : {},
222
+ })
223
+ }
224
+ >
225
+ <HistoryIcon
226
+ className={cx('byline-view-menu-icon-button-icon', styles.iconButtonIcon)}
227
+ />
228
+ </IconButton>
229
+ </Tooltip>
230
+ <Button
231
+ size="xs"
232
+ variant={activeView === 'edit' ? 'filled' : 'outlined'}
233
+ className={cx('byline-view-menu-button', styles.button)}
234
+ onClick={() =>
235
+ navigate({
236
+ to: getAdminRoutePath('collections', '$collection', '$id'),
237
+ params: { collection, id: documentId },
238
+ search: locale ? { locale } : {},
239
+ })
240
+ }
241
+ >
242
+ {t('common.actions.edit')}
243
+ </Button>
244
+ <Button
245
+ size="xs"
246
+ variant={activeView === 'api' ? 'filled' : 'outlined'}
247
+ className={cx('byline-view-menu-button', styles.button)}
248
+ onClick={() =>
249
+ navigate({
250
+ to: getAdminRoutePath('collections', '$collection', '$id', 'api'),
251
+ params: { collection, id: documentId },
252
+ search: locale ? { locale } : {},
253
+ })
254
+ }
255
+ >
256
+ {t('collections.viewMenu.apiButton')}
257
+ </Button>
258
+ </div>
241
259
  </div>
242
260
  )
243
261
  }
@@ -1,7 +1,4 @@
1
- import { DocumentMutationNotice } from '../document-mutation-notice.js'
2
- import { useDocumentMutationState } from '../document-mutation-state.js'
3
-
4
- ;('use client')
1
+ 'use client'
5
2
 
6
3
  /**
7
4
  * This Source Code is subject to the terms of the Mozilla Public
@@ -32,6 +29,8 @@ import { getAdminRoutePath } from '../../routes/admin-path.js'
32
29
  import { cancelCollectionDocumentScheduledPublish } from '../../server-fns/collections/index.js'
33
30
  import { Link, useNavigate } from '../chrome/loose-router.js'
34
31
  import { RouterPager } from '../chrome/router-pager.js'
32
+ import { DocumentMutationNotice } from '../document-mutation-notice.js'
33
+ import { useDocumentMutationState } from '../document-mutation-state.js'
35
34
  import styles from './list.module.css'
36
35
  import type { ScheduledPublicationListResponse } from '../../server-fns/collections/index.js'
37
36
 
@@ -372,7 +372,7 @@ describe('singleton version history', () => {
372
372
  <HistoryView
373
373
  collectionDefinition={collectionDefinition}
374
374
  adminConfig={{
375
- slug: 'articles',
375
+ path: 'articles',
376
376
  columns: [{ fieldName: 'title', label: 'Title' }],
377
377
  }}
378
378
  data={data as never}
@@ -12,7 +12,7 @@ import { useState } from 'react'
12
12
 
13
13
  import type { SingletonAdminConfig, SingletonPreviewDocument } from '@byline/core'
14
14
  import { useTranslation } from '@byline/i18n/react'
15
- import { ExternalLinkIcon, IconButton, useToastManager } from '@byline/ui/react'
15
+ import { ExternalLinkIcon, IconButton, Tooltip, useToastManager } from '@byline/ui/react'
16
16
  import cx from 'clsx'
17
17
 
18
18
  import { enablePreviewModeFn } from '../../server-fns/preview/index.js'
@@ -60,16 +60,17 @@ export function SingletonPreviewLink({
60
60
  }
61
61
 
62
62
  return (
63
- <IconButton
64
- className={cx('byline-preview-link', className)}
65
- size="xs"
66
- variant="text"
67
- disabled={busy}
68
- onClick={handleClick}
69
- aria-label={t('collections.preview.openAriaLabel')}
70
- title={t('collections.preview.title')}
71
- >
72
- <ExternalLinkIcon width="20px" height="20px" className="byline-preview-link-icon" />
73
- </IconButton>
63
+ <Tooltip text={t('collections.preview.title')} side="bottom" delay={0}>
64
+ <IconButton
65
+ className={cx('byline-preview-link', className)}
66
+ size="xs"
67
+ variant="text"
68
+ disabled={busy}
69
+ onClick={handleClick}
70
+ aria-label={t('collections.preview.openAriaLabel')}
71
+ >
72
+ <ExternalLinkIcon width="20px" height="20px" className="byline-preview-link-icon" />
73
+ </IconButton>
74
+ </Tooltip>
74
75
  )
75
76
  }
@@ -12,7 +12,7 @@ import { useEffect } from 'react'
12
12
 
13
13
  import type { SingletonAdminConfig, SingletonPreviewDocument } from '@byline/core'
14
14
  import { useTranslation } from '@byline/i18n/react'
15
- import { Button, HistoryIcon, IconButton, Label, Select } from '@byline/ui/react'
15
+ import { Button, HistoryIcon, IconButton, Label, Select, Tooltip } from '@byline/ui/react'
16
16
  import cx from 'clsx'
17
17
 
18
18
  import { getAdminRoutePath } from '../../routes/admin-path.js'
@@ -71,66 +71,72 @@ export function SingletonViewMenu({
71
71
 
72
72
  return (
73
73
  <div className={cx('byline-view-menu', collectionStyles.root)}>
74
- <Label
75
- className={cx('muted byline-view-menu-label', collectionStyles.label)}
76
- id="contentLocaleLabel"
77
- htmlFor="contentLocale"
78
- label={t('collections.viewMenu.contentLocaleLabel')}
79
- />
80
- <Select<string>
81
- name="contentLocale"
82
- id="contentLocale"
83
- className={cx('byline-view-menu-locale-select', collectionStyles.localeSelect)}
84
- size="xs"
85
- variant="outlined"
86
- value={locale ?? defaultContentLocale}
87
- items={[
88
- ...(activeView === 'history'
89
- ? [{ value: 'all', label: t('collections.viewMenu.localeAll') }]
90
- : []),
91
- ...contentLocales.map((entry) => ({ value: entry.code, label: entry.label })),
92
- ]}
93
- onValueChange={handleLocaleChange}
94
- />
95
- {adminConfig?.preview != null && doc != null && (
96
- <SingletonPreviewLink
97
- doc={doc}
98
- adminConfig={adminConfig}
99
- locale={locale}
100
- className={cx('byline-view-menu-icon-button', collectionStyles.iconButton)}
74
+ <div className={cx('byline-view-menu-data', collectionStyles.group)}>
75
+ <Label
76
+ className={cx('muted byline-view-menu-label', collectionStyles.label)}
77
+ id="contentLocaleLabel"
78
+ htmlFor="contentLocale"
79
+ label={t('collections.viewMenu.contentLocaleLabel')}
101
80
  />
102
- )}
103
- <IconButton
104
- className={cx('byline-view-menu-icon-button', collectionStyles.iconButton)}
105
- size="xs"
106
- variant={activeView === 'history' ? 'filled' : 'text'}
107
- aria-label={t('collections.breadcrumbs.history')}
108
- onClick={() =>
109
- navigate({
110
- to: getAdminRoutePath('singletons', '$singleton', 'history'),
111
- params: { singleton },
112
- search: locale ? { locale } : {},
113
- })
114
- }
115
- >
116
- <HistoryIcon
117
- className={cx('byline-view-menu-icon-button-icon', collectionStyles.iconButtonIcon)}
81
+ <Select<string>
82
+ name="contentLocale"
83
+ id="contentLocale"
84
+ className={cx('byline-view-menu-locale-select', collectionStyles.localeSelect)}
85
+ size="xs"
86
+ variant="outlined"
87
+ value={locale ?? defaultContentLocale}
88
+ items={[
89
+ ...(activeView === 'history'
90
+ ? [{ value: 'all', label: t('collections.viewMenu.localeAll') }]
91
+ : []),
92
+ ...contentLocales.map((entry) => ({ value: entry.code, label: entry.label })),
93
+ ]}
94
+ onValueChange={handleLocaleChange}
118
95
  />
119
- </IconButton>
120
- <Button
121
- size="xs"
122
- variant={activeView === 'edit' ? 'filled' : 'outlined'}
123
- className={cx('byline-view-menu-button', collectionStyles.button)}
124
- onClick={() =>
125
- navigate({
126
- to: getAdminRoutePath('singletons', '$singleton'),
127
- params: { singleton },
128
- search: locale ? { locale } : {},
129
- })
130
- }
131
- >
132
- {t('common.actions.edit')}
133
- </Button>
96
+ </div>
97
+ <div className={cx('byline-view-menu-nav', collectionStyles.group)}>
98
+ {adminConfig?.preview != null && doc != null && (
99
+ <SingletonPreviewLink
100
+ doc={doc}
101
+ adminConfig={adminConfig}
102
+ locale={locale}
103
+ className={cx('byline-view-menu-icon-button', collectionStyles.iconButton)}
104
+ />
105
+ )}
106
+ <Tooltip text={t('collections.breadcrumbs.history')} side="bottom" delay={0}>
107
+ <IconButton
108
+ className={cx('byline-view-menu-icon-button', collectionStyles.iconButton)}
109
+ size="xs"
110
+ variant={activeView === 'history' ? 'filled' : 'text'}
111
+ aria-label={t('collections.breadcrumbs.history')}
112
+ onClick={() =>
113
+ navigate({
114
+ to: getAdminRoutePath('singletons', '$singleton', 'history'),
115
+ params: { singleton },
116
+ search: locale ? { locale } : {},
117
+ })
118
+ }
119
+ >
120
+ <HistoryIcon
121
+ className={cx('byline-view-menu-icon-button-icon', collectionStyles.iconButtonIcon)}
122
+ />
123
+ </IconButton>
124
+ </Tooltip>
125
+ <Button
126
+ size="xs"
127
+ variant={activeView === 'edit' ? 'filled' : 'outlined'}
128
+ className={cx('byline-view-menu-button', collectionStyles.button)}
129
+ onClick={() =>
130
+ navigate({
131
+ to: getAdminRoutePath('singletons', '$singleton'),
132
+ params: { singleton },
133
+ search: locale ? { locale } : {},
134
+ })
135
+ }
136
+ >
137
+ {t('common.actions.edit')}
138
+ </Button>
139
+ </div>
134
140
  </div>
135
141
  )
136
142
  }
@@ -142,7 +142,7 @@ const singleton = defineSingleton({
142
142
 
143
143
  const adminConfig = {
144
144
  singleton: true as const,
145
- slug: singleton.path,
145
+ path: singleton.path,
146
146
  preview: { url: () => '/preview-settings' },
147
147
  }
148
148
 
@@ -181,7 +181,7 @@ let root: Root
181
181
  function view(
182
182
  currentDocument: typeof loadedDocument | null,
183
183
  locale = 'en',
184
- config: typeof adminConfig | { singleton: true; slug: string } = adminConfig
184
+ config: typeof adminConfig | { singleton: true; path: string } = adminConfig
185
185
  ) {
186
186
  return (
187
187
  <I18nProvider
@@ -208,7 +208,7 @@ function view(
208
208
  function render(
209
209
  currentDocument: typeof loadedDocument | null,
210
210
  locale = 'en',
211
- config: typeof adminConfig | { singleton: true; slug: string } = adminConfig
211
+ config: typeof adminConfig | { singleton: true; path: string } = adminConfig
212
212
  ) {
213
213
  act(() => root.render(view(currentDocument, locale, config)))
214
214
  }
@@ -332,7 +332,7 @@ describe('SingletonView', () => {
332
332
  })
333
333
 
334
334
  it('never derives a singleton preview from its internal stored path', () => {
335
- render(loadedDocument, 'en', { singleton: true, slug: singleton.path })
335
+ render(loadedDocument, 'en', { singleton: true, path: singleton.path })
336
336
 
337
337
  expect(container.querySelector('.byline-view-menu')).not.toBeNull()
338
338
  expect(container.querySelector('.byline-preview-link')).toBeNull()
@@ -11,7 +11,7 @@
11
11
  import { useRouter } from '@tanstack/react-router'
12
12
 
13
13
  import { FormRenderer } from '@byline/admin/react'
14
- import type { SingletonAdminConfig, SingletonDefinition } from '@byline/core'
14
+ import type { SingletonAdminConfig, SingletonDefinition, UnavailableBlock } from '@byline/core'
15
15
  import {
16
16
  getDefaultStatus,
17
17
  getWorkflow,
@@ -82,6 +82,7 @@ export function SingletonView({
82
82
  ? workflowStatuses[currentIndex + 1]
83
83
  : undefined
84
84
  const publishedVersion = document?._publishedVersion ?? null
85
+ const unavailableBlocks = document?._unavailableBlocks as UnavailableBlock[] | undefined
85
86
  const restoreWarnings = document?._restoreWarnings as string[] | undefined
86
87
  const scheduledPublicationEnabled = document?._scheduledPublicationEnabled === true
87
88
  const canSchedulePublication = document?._canSchedulePublication === true
@@ -441,6 +442,7 @@ export function SingletonView({
441
442
  contentLocales={document == null ? undefined : contentLocales}
442
443
  publishedVersion={document == null ? null : publishedVersion}
443
444
  restoreWarnings={restoreWarnings}
445
+ unavailableBlocks={unavailableBlocks}
444
446
  nextStatus={document == null ? undefined : nextStatus}
445
447
  workflowStatuses={document == null ? undefined : workflowStatuses}
446
448
  onCancel={() => navigate({ to: getAdminRoutePath() })}
@@ -120,6 +120,8 @@ const getDocumentFn = createServerFn({ method: 'GET' })
120
120
  | string[]
121
121
  | undefined
122
122
 
123
+ const unavailableBlocks = document._unavailableBlocks
124
+
123
125
  // Same preservation for the available-locales metadata — both are stripped
124
126
  // by the Zod parse but the sidebar widget needs them at edit time:
125
127
  // `availableLocales` — the stored editorial set → initialises the
@@ -155,6 +157,7 @@ const getDocumentFn = createServerFn({ method: 'GET' })
155
157
  return {
156
158
  ...withEditableRevision(parsed, document),
157
159
  _publishedVersion: publishedVersion,
160
+ ...(unavailableBlocks?.length ? { _unavailableBlocks: unavailableBlocks } : {}),
158
161
  ...(restoreWarnings && restoreWarnings.length > 0
159
162
  ? { _restoreWarnings: restoreWarnings }
160
163
  : {}),