@byline/host-tanstack-start 4.16.0 → 4.17.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 (146) hide show
  1. package/dist/admin-shell/chrome/admin-menu-drawer.js +14 -14
  2. package/dist/admin-shell/chrome/dashboard.js +41 -5
  3. package/dist/admin-shell/chrome/dashboard.test.d.ts +8 -0
  4. package/dist/admin-shell/collections/api.d.ts +2 -2
  5. package/dist/admin-shell/collections/create.d.ts +2 -2
  6. package/dist/admin-shell/collections/create.js +1 -0
  7. package/dist/admin-shell/collections/edit.d.ts +2 -2
  8. package/dist/admin-shell/collections/edit.js +1 -0
  9. package/dist/admin-shell/collections/history.d.ts +4 -12
  10. package/dist/admin-shell/collections/history.js +112 -303
  11. package/dist/admin-shell/collections/reindex-button.d.ts +1 -1
  12. package/dist/admin-shell/collections/reindex-button.js +5 -1
  13. package/dist/admin-shell/collections/restore-version-modal.d.ts +4 -4
  14. package/dist/admin-shell/collections/restore-version-modal.js +8 -24
  15. package/dist/admin-shell/collections/version-history.d.ts +72 -0
  16. package/dist/admin-shell/collections/version-history.js +259 -0
  17. package/dist/admin-shell/singletons/history.d.ts +19 -0
  18. package/dist/admin-shell/singletons/history.js +170 -0
  19. package/dist/admin-shell/singletons/history.test.d.ts +8 -0
  20. package/dist/admin-shell/singletons/preview-link.d.ts +12 -0
  21. package/dist/admin-shell/singletons/preview-link.js +54 -0
  22. package/dist/admin-shell/singletons/view-menu.d.ts +13 -0
  23. package/dist/admin-shell/singletons/view-menu.js +116 -0
  24. package/dist/admin-shell/singletons/view.d.ts +18 -0
  25. package/dist/admin-shell/singletons/view.js +259 -0
  26. package/dist/admin-shell/singletons/view.test.d.ts +8 -0
  27. package/dist/integrations/api-utils.d.ts +18 -4
  28. package/dist/integrations/api-utils.js +10 -3
  29. package/dist/routes/create-admin-dashboard-route.js +2 -2
  30. package/dist/routes/create-collection-api-route.js +3 -3
  31. package/dist/routes/create-collection-create-route.js +4 -3
  32. package/dist/routes/create-collection-edit-route.js +4 -3
  33. package/dist/routes/create-collection-history-route.js +4 -3
  34. package/dist/routes/create-collection-list-route.js +4 -3
  35. package/dist/routes/create-singleton-history-route.d.ts +8 -0
  36. package/dist/routes/create-singleton-history-route.js +99 -0
  37. package/dist/routes/create-singleton-route.d.ts +35 -0
  38. package/dist/routes/create-singleton-route.js +130 -0
  39. package/dist/routes/get-multi-collection-definition.d.ts +10 -0
  40. package/dist/routes/get-multi-collection-definition.js +6 -0
  41. package/dist/routes/get-singleton-definition.d.ts +10 -0
  42. package/dist/routes/get-singleton-definition.js +6 -0
  43. package/dist/routes/index.d.ts +2 -0
  44. package/dist/routes/index.js +2 -0
  45. package/dist/server-fns/actors.d.ts +16 -0
  46. package/dist/server-fns/actors.js +25 -0
  47. package/dist/server-fns/admin-document-presentation.d.ts +30 -0
  48. package/dist/server-fns/admin-document-presentation.js +32 -0
  49. package/dist/server-fns/collections/actors.d.ts +1 -10
  50. package/dist/server-fns/collections/actors.js +1 -25
  51. package/dist/server-fns/collections/get.js +9 -26
  52. package/dist/server-fns/collections/reorder.js +1 -1
  53. package/dist/server-fns/collections/scheduled-publication.js +2 -2
  54. package/dist/server-fns/collections/upload.js +3 -3
  55. package/dist/server-fns/collections/utils.d.ts +1 -7
  56. package/dist/server-fns/collections/utils.js +1 -4
  57. package/dist/server-fns/serialise.d.ts +14 -0
  58. package/dist/server-fns/serialise.js +4 -0
  59. package/dist/server-fns/singleton-document-read.d.ts +20 -0
  60. package/dist/server-fns/singleton-document-read.js +10 -0
  61. package/dist/server-fns/singletons/cancel-scheduled-publish.d.ts +12 -0
  62. package/dist/server-fns/singletons/cancel-scheduled-publish.js +7 -0
  63. package/dist/server-fns/singletons/change-status.d.ts +14 -0
  64. package/dist/server-fns/singletons/change-status.js +7 -0
  65. package/dist/server-fns/singletons/confirm-scheduled-publish.d.ts +14 -0
  66. package/dist/server-fns/singletons/confirm-scheduled-publish.js +9 -0
  67. package/dist/server-fns/singletons/copy-to-locale.d.ts +18 -0
  68. package/dist/server-fns/singletons/copy-to-locale.js +11 -0
  69. package/dist/server-fns/singletons/find-by-version.d.ts +16 -0
  70. package/dist/server-fns/singletons/find-by-version.js +9 -0
  71. package/dist/server-fns/singletons/get-scheduled-publish.d.ts +12 -0
  72. package/dist/server-fns/singletons/get-scheduled-publish.js +7 -0
  73. package/dist/server-fns/singletons/get.d.ts +24 -0
  74. package/dist/server-fns/singletons/get.js +6 -0
  75. package/dist/server-fns/singletons/history.d.ts +31 -0
  76. package/dist/server-fns/singletons/history.js +22 -0
  77. package/dist/server-fns/singletons/index.d.ts +19 -0
  78. package/dist/server-fns/singletons/index.js +12 -0
  79. package/dist/server-fns/singletons/restore-version.d.ts +14 -0
  80. package/dist/server-fns/singletons/restore-version.js +7 -0
  81. package/dist/server-fns/singletons/schedule-publish.d.ts +16 -0
  82. package/dist/server-fns/singletons/schedule-publish.js +10 -0
  83. package/dist/server-fns/singletons/unpublish.d.ts +12 -0
  84. package/dist/server-fns/singletons/unpublish.js +7 -0
  85. package/dist/server-fns/singletons/update.d.ts +14 -0
  86. package/dist/server-fns/singletons/update.js +14 -0
  87. package/dist/vitest-jsdom.test.d.ts +8 -0
  88. package/package.json +24 -18
  89. package/src/admin-shell/chrome/admin-menu-drawer.tsx +13 -13
  90. package/src/admin-shell/chrome/dashboard.test.tsx +199 -0
  91. package/src/admin-shell/chrome/dashboard.tsx +43 -9
  92. package/src/admin-shell/collections/api.tsx +2 -2
  93. package/src/admin-shell/collections/create.tsx +6 -2
  94. package/src/admin-shell/collections/edit.tsx +6 -2
  95. package/src/admin-shell/collections/history.module.css +1 -1
  96. package/src/admin-shell/collections/history.tsx +162 -442
  97. package/src/admin-shell/collections/reindex-button.tsx +4 -2
  98. package/src/admin-shell/collections/restore-version-modal.tsx +19 -25
  99. package/src/admin-shell/collections/version-history.tsx +349 -0
  100. package/src/admin-shell/singletons/history.test.tsx +407 -0
  101. package/src/admin-shell/singletons/history.tsx +185 -0
  102. package/src/admin-shell/singletons/preview-link.tsx +75 -0
  103. package/src/admin-shell/singletons/view-menu.tsx +136 -0
  104. package/src/admin-shell/singletons/view.test.tsx +400 -0
  105. package/src/admin-shell/singletons/view.tsx +351 -0
  106. package/src/integrations/api-utils.ts +34 -9
  107. package/src/routes/create-admin-dashboard-route.test.node.ts +70 -0
  108. package/src/routes/create-admin-dashboard-route.tsx +10 -2
  109. package/src/routes/create-collection-api-route.tsx +4 -4
  110. package/src/routes/create-collection-create-route.tsx +5 -8
  111. package/src/routes/create-collection-edit-route.tsx +5 -4
  112. package/src/routes/create-collection-history-route.tsx +5 -8
  113. package/src/routes/create-collection-list-route.tsx +5 -8
  114. package/src/routes/create-singleton-history-route.test.node.ts +148 -0
  115. package/src/routes/create-singleton-history-route.tsx +116 -0
  116. package/src/routes/create-singleton-route.test.node.ts +172 -0
  117. package/src/routes/create-singleton-route.tsx +176 -0
  118. package/src/routes/get-multi-collection-definition.ts +16 -0
  119. package/src/routes/get-singleton-definition.ts +16 -0
  120. package/src/routes/index.ts +2 -0
  121. package/src/server-fns/actors.ts +43 -0
  122. package/src/server-fns/admin-document-presentation.ts +78 -0
  123. package/src/server-fns/collections/actors.ts +1 -52
  124. package/src/server-fns/collections/get.ts +10 -49
  125. package/src/server-fns/collections/reorder.ts +1 -1
  126. package/src/server-fns/collections/scheduled-publication.ts +7 -1
  127. package/src/server-fns/collections/upload-singleton.test.node.ts +164 -0
  128. package/src/server-fns/collections/upload.ts +7 -3
  129. package/src/server-fns/collections/utils.ts +1 -9
  130. package/src/server-fns/serialise.ts +17 -0
  131. package/src/server-fns/singleton-document-read.ts +32 -0
  132. package/src/server-fns/singletons/cancel-scheduled-publish.ts +19 -0
  133. package/src/server-fns/singletons/change-status.ts +19 -0
  134. package/src/server-fns/singletons/confirm-scheduled-publish.ts +23 -0
  135. package/src/server-fns/singletons/copy-to-locale.ts +32 -0
  136. package/src/server-fns/singletons/find-by-version.ts +23 -0
  137. package/src/server-fns/singletons/get-scheduled-publish.ts +19 -0
  138. package/src/server-fns/singletons/get.ts +32 -0
  139. package/src/server-fns/singletons/history.ts +41 -0
  140. package/src/server-fns/singletons/index.ts +20 -0
  141. package/src/server-fns/singletons/restore-version.ts +19 -0
  142. package/src/server-fns/singletons/schedule-publish.ts +24 -0
  143. package/src/server-fns/singletons/singletons.test.node.ts +298 -0
  144. package/src/server-fns/singletons/unpublish.ts +19 -0
  145. package/src/server-fns/singletons/update.ts +31 -0
  146. package/src/vitest-jsdom.test.tsx +27 -0
@@ -0,0 +1,199 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ import type React from 'react'
10
+ import { act } from 'react'
11
+
12
+ import { defineAdminConfig, defineSingleton, type MultiCollectionDefinition } from '@byline/core'
13
+ import { createRoot } from 'react-dom/client'
14
+ import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
15
+
16
+ const abilityState = vi.hoisted(() => ({
17
+ isSuperAdmin: false,
18
+ abilities: [] as string[],
19
+ }))
20
+
21
+ vi.mock('../../integrations/abilities.jsx', () => ({
22
+ useAbilities: () => ({
23
+ isSuperAdmin: abilityState.isSuperAdmin,
24
+ abilities: abilityState.abilities,
25
+ has: () => false,
26
+ hasAny: () => false,
27
+ }),
28
+ }))
29
+
30
+ vi.mock('@byline/i18n/react', () => ({
31
+ useTranslation: () => ({
32
+ t: (key: string, values?: Record<string, string | number>) => {
33
+ if (key === 'dashboard.totalCount') return `${values?.count} total`
34
+ if (key === 'dashboard.collectionDescription') return `Manage ${values?.label}`
35
+ return key
36
+ },
37
+ }),
38
+ }))
39
+
40
+ vi.mock('@byline/ui/react', () => {
41
+ type Props = { children?: React.ReactNode; className?: string }
42
+ const element = (tag: keyof React.JSX.IntrinsicElements) =>
43
+ function Element({ children, ...props }: Props) {
44
+ const Component = tag
45
+ return <Component {...props}>{children}</Component>
46
+ }
47
+ const Card = element('article') as ReturnType<typeof element> & {
48
+ Header: ReturnType<typeof element>
49
+ Title: ReturnType<typeof element>
50
+ Description: ReturnType<typeof element>
51
+ Content: ReturnType<typeof element>
52
+ }
53
+ Card.Header = element('header')
54
+ Card.Title = element('h3')
55
+ Card.Description = element('p')
56
+ Card.Content = element('div')
57
+ return { Card, Container: element('div'), Section: element('section') }
58
+ })
59
+
60
+ vi.mock('./loose-router.js', () => ({
61
+ Link: ({
62
+ to = '',
63
+ params = {},
64
+ search: _search,
65
+ children,
66
+ ...props
67
+ }: React.AnchorHTMLAttributes<HTMLAnchorElement> & {
68
+ to?: string
69
+ params?: Record<string, string>
70
+ search?: unknown
71
+ }) => {
72
+ let href = to
73
+ for (const [name, value] of Object.entries(params)) {
74
+ href = href.replace(`$${name}`, value)
75
+ }
76
+ return (
77
+ <a href={href} {...props}>
78
+ {children}
79
+ </a>
80
+ )
81
+ },
82
+ }))
83
+
84
+ import { AdminDashboard } from './dashboard.js'
85
+
86
+ const articles: MultiCollectionDefinition = {
87
+ path: 'articles',
88
+ labels: { singular: 'Article', plural: 'Articles' },
89
+ fields: [{ name: 'title', label: 'Title', type: 'text' }],
90
+ showStats: true,
91
+ }
92
+
93
+ const settings = defineSingleton({
94
+ path: 'site-settings',
95
+ label: 'Site settings',
96
+ fields: [{ name: 'title', label: 'Title', type: 'text' }],
97
+ })
98
+
99
+ function renderDashboard(statsMap: Record<string, Array<{ status: string; count: number }>> = {}) {
100
+ const container = document.createElement('div')
101
+ document.body.append(container)
102
+ const root = createRoot(container)
103
+ act(() => root.render(<AdminDashboard statsMap={statsMap} />))
104
+ return {
105
+ container,
106
+ dispose() {
107
+ act(() => root.unmount())
108
+ container.remove()
109
+ },
110
+ }
111
+ }
112
+
113
+ describe('AdminDashboard document resources', () => {
114
+ beforeAll(() => {
115
+ ;(
116
+ globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
117
+ ).IS_REACT_ACT_ENVIRONMENT = true
118
+ })
119
+
120
+ afterAll(() => {
121
+ delete (globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean })
122
+ .IS_REACT_ACT_ENVIRONMENT
123
+ })
124
+
125
+ beforeEach(() => {
126
+ abilityState.isSuperAdmin = false
127
+ abilityState.abilities = []
128
+ defineAdminConfig({
129
+ routes: { admin: '/internal/cms' },
130
+ collections: [articles, settings],
131
+ admin: [
132
+ { slug: articles.path, group: 'content' },
133
+ { singleton: true, slug: settings.path, group: 'content' },
134
+ ],
135
+ collectionGroups: [{ name: 'content', label: 'Content' }],
136
+ i18n: {
137
+ admin: { defaultLocale: 'en', locales: [] },
138
+ content: { defaultLocale: 'en', locales: [] },
139
+ },
140
+ })
141
+ })
142
+
143
+ it('shows a singleton only for its kind-aware read ability', () => {
144
+ abilityState.abilities = ['singletons.site-settings.read']
145
+ const allowed = renderDashboard()
146
+ expect(allowed.container.textContent).toContain('Site settings')
147
+ expect(allowed.container.textContent).not.toContain('Articles')
148
+ allowed.dispose()
149
+
150
+ abilityState.abilities = ['collections.site-settings.read']
151
+ const staleNamespace = renderDashboard()
152
+ expect(staleNamespace.container.textContent).not.toContain('Site settings')
153
+ staleNamespace.dispose()
154
+ })
155
+
156
+ it('shows both resource kinds to a super admin in registry order', () => {
157
+ abilityState.isSuperAdmin = true
158
+ const view = renderDashboard({ articles: [{ status: 'draft', count: 3 }] })
159
+
160
+ const titles = [...view.container.querySelectorAll('.byline-dashboard-title-text')].map(
161
+ (element) => element.textContent
162
+ )
163
+ expect(titles).toEqual(['Articles', 'Site settings'])
164
+ expect(view.container.querySelector('.byline-dashboard-group-heading')?.textContent).toBe(
165
+ 'Content'
166
+ )
167
+ view.dispose()
168
+ })
169
+
170
+ it('links a singleton under the custom admin base without collection stats', () => {
171
+ abilityState.isSuperAdmin = true
172
+ const view = renderDashboard({
173
+ articles: [{ status: 'draft', count: 3 }],
174
+ 'site-settings': [{ status: 'published', count: 99 }],
175
+ })
176
+
177
+ const link = view.container.querySelector<HTMLAnchorElement>(
178
+ 'a[href="/internal/cms/singletons/site-settings"]'
179
+ )
180
+ const card = link?.closest('article')
181
+ expect(link).not.toBeNull()
182
+ expect(card?.querySelector('.byline-dashboard-stat-tile')).toBeNull()
183
+ expect(card?.querySelector('.byline-dashboard-title-meta')).toBeNull()
184
+ expect(card?.textContent).not.toContain('99')
185
+ expect(card?.textContent?.match(/Manage Site settings/g)).toHaveLength(1)
186
+ expect(
187
+ view.container.querySelector('a[href="/internal/cms/collections/articles"]')
188
+ ).not.toBeNull()
189
+ expect(view.container.querySelector('.byline-dashboard-stat-tile')).not.toBeNull()
190
+ view.dispose()
191
+ })
192
+
193
+ it('hides an update-only singleton role from the dashboard', () => {
194
+ abilityState.abilities = ['singletons.site-settings.update']
195
+ const view = renderDashboard()
196
+ expect(view.container.textContent).not.toContain('Site settings')
197
+ view.dispose()
198
+ })
199
+ })
@@ -6,12 +6,13 @@
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
8
 
9
- import type { CollectionDefinition, WorkflowStatus } from '@byline/core'
9
+ import type { MultiCollectionDefinition, SingletonDefinition, WorkflowStatus } from '@byline/core'
10
10
  import {
11
11
  filterReadableCollections,
12
12
  getAdminConfig,
13
13
  getWorkflowStatuses,
14
14
  groupCollectionsForAdmin,
15
+ isSingleton,
15
16
  } from '@byline/core'
16
17
  import { useTranslation } from '@byline/i18n/react'
17
18
  import { Card, Container, Section } from '@byline/ui/react'
@@ -68,7 +69,7 @@ function CollectionCard({
68
69
  collection,
69
70
  stats,
70
71
  }: {
71
- collection: CollectionDefinition
72
+ collection: MultiCollectionDefinition
72
73
  stats: CollectionStatusCount[] | undefined
73
74
  }) {
74
75
  const { t } = useTranslation('byline-admin')
@@ -134,6 +135,35 @@ function CollectionCard({
134
135
  )
135
136
  }
136
137
 
138
+ function SingletonCard({ singleton }: { singleton: SingletonDefinition }) {
139
+ const { t } = useTranslation('byline-admin')
140
+ const destination = getAdminRoutePath('singletons', '$singleton')
141
+ const params = { singleton: singleton.path }
142
+
143
+ return (
144
+ <Card>
145
+ <Link
146
+ to={destination}
147
+ params={params}
148
+ className={cx('byline-dashboard-card-link', styles.cardLink)}
149
+ >
150
+ <Card.Header>
151
+ <div className={cx('byline-dashboard-card-header', styles.cardHeader)}>
152
+ <Card.Title className={cx('byline-dashboard-card-title', styles.cardTitle)}>
153
+ <span className={cx('byline-dashboard-title-text', styles.titleText)}>
154
+ {singleton.label}
155
+ </span>
156
+ </Card.Title>
157
+ <Card.Description className="muted">
158
+ {t('dashboard.collectionDescription', { label: singleton.label })}
159
+ </Card.Description>
160
+ </div>
161
+ </Card.Header>
162
+ </Link>
163
+ </Card>
164
+ )
165
+ }
166
+
137
167
  interface AdminDashboardProps {
138
168
  statsMap: Record<string, CollectionStatusCount[]>
139
169
  }
@@ -173,13 +203,17 @@ export function AdminDashboard({ statsMap }: AdminDashboardProps) {
173
203
  </h2>
174
204
  )}
175
205
  <div className={cx('byline-dashboard-grid', styles.grid)}>
176
- {bucket.collections.map((collection) => (
177
- <CollectionCard
178
- key={collection.path}
179
- collection={collection}
180
- stats={statsMap[collection.path]}
181
- />
182
- ))}
206
+ {bucket.collections.map((resource) =>
207
+ isSingleton(resource) ? (
208
+ <SingletonCard key={resource.path} singleton={resource} />
209
+ ) : (
210
+ <CollectionCard
211
+ key={resource.path}
212
+ collection={resource}
213
+ stats={statsMap[resource.path]}
214
+ />
215
+ )
216
+ )}
183
217
  </div>
184
218
  </section>
185
219
  ))}
@@ -6,7 +6,7 @@
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
8
 
9
- import type { CollectionDefinition } from '@byline/core'
9
+ import type { MultiCollectionDefinition } from '@byline/core'
10
10
  import type { AnyCollectionSchemaTypes } from '@byline/core/zod-schemas'
11
11
  import { useTranslation } from '@byline/i18n/react'
12
12
  import { Container, Section } from '@byline/ui/react'
@@ -32,7 +32,7 @@ export const ApiView = ({
32
32
  contentLocales,
33
33
  defaultContentLocale,
34
34
  }: {
35
- collectionDefinition: CollectionDefinition
35
+ collectionDefinition: MultiCollectionDefinition
36
36
  initialData: AnyCollectionSchemaTypes['UpdateType']
37
37
  /** Currently active locale from the route search param (undefined → all). */
38
38
  locale?: string
@@ -9,7 +9,7 @@
9
9
  import { useState } from 'react'
10
10
 
11
11
  import { FormRenderer } from '@byline/admin/react'
12
- import type { CollectionAdminConfig, CollectionDefinition } from '@byline/core'
12
+ import type { CollectionAdminConfig, MultiCollectionDefinition } from '@byline/core'
13
13
  import { useTranslation } from '@byline/i18n/react'
14
14
  import { Container, Section, useToastManager } from '@byline/ui/react'
15
15
 
@@ -30,7 +30,7 @@ export const CreateView = ({
30
30
  initialData,
31
31
  from,
32
32
  }: {
33
- collectionDefinition: CollectionDefinition
33
+ collectionDefinition: MultiCollectionDefinition
34
34
  adminConfig?: CollectionAdminConfig
35
35
  // biome-ignore lint/suspicious/noExplicitAny: shape is collection-specific
36
36
  initialData?: Record<string, any>
@@ -105,6 +105,10 @@ export const CreateView = ({
105
105
  status: 'failed',
106
106
  message: description,
107
107
  })
108
+
109
+ // Rethrow so FormRenderer keeps the form dirty. The toast above has
110
+ // already told the editor what went wrong.
111
+ throw err
108
112
  }
109
113
  }
110
114
 
@@ -9,7 +9,7 @@
9
9
  import { useState } from 'react'
10
10
 
11
11
  import { FormRenderer } from '@byline/admin/react'
12
- import type { CollectionAdminConfig, CollectionDefinition } from '@byline/core'
12
+ import type { CollectionAdminConfig, MultiCollectionDefinition } from '@byline/core'
13
13
  import {
14
14
  getDefaultStatus,
15
15
  getWorkflow,
@@ -56,7 +56,7 @@ export const EditView = ({
56
56
  defaultContentLocale,
57
57
  returnSearch,
58
58
  }: {
59
- collectionDefinition: CollectionDefinition
59
+ collectionDefinition: MultiCollectionDefinition
60
60
  adminConfig?: CollectionAdminConfig
61
61
  initialData: AnyCollectionSchemaTypes['UpdateType']
62
62
  locale?: string
@@ -623,6 +623,10 @@ export const EditView = ({
623
623
  })
624
624
 
625
625
  setEditState({ status: 'failed', message: description })
626
+
627
+ // Rethrow so FormRenderer keeps the form dirty. The toast above has
628
+ // already told the editor what went wrong.
629
+ throw err
626
630
  }
627
631
  }
628
632
 
@@ -1,5 +1,5 @@
1
1
  /**
2
- * HistoryView — list of past versions for a collection document.
2
+ * Shared version-history presentation for collection documents and singletons.
3
3
  *
4
4
  * Override handles:
5
5
  * .byline-coll-history-head — title + view-menu row