@prenta/admin 2.7.0 → 2.8.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 (163) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/design-system/tokens.css +17 -0
  3. package/design-system/tokens.json +35 -1
  4. package/design-system/tokens.scss +15 -0
  5. package/design-system/tokens.tailwind.cjs +14 -0
  6. package/dist/AdminRoot.d.ts.map +1 -1
  7. package/dist/AdminRoot.js +2 -2
  8. package/dist/AdminRoot.js.map +1 -1
  9. package/dist/__tests__/lib/batch-wizard.test.d.ts +2 -0
  10. package/dist/__tests__/lib/batch-wizard.test.d.ts.map +1 -0
  11. package/dist/__tests__/lib/batch-wizard.test.js +144 -0
  12. package/dist/__tests__/lib/batch-wizard.test.js.map +1 -0
  13. package/dist/__tests__/lib/job-drawer.test.js +2 -0
  14. package/dist/__tests__/lib/job-drawer.test.js.map +1 -1
  15. package/dist/__tests__/lib/job-view.test.js +100 -3
  16. package/dist/__tests__/lib/job-view.test.js.map +1 -1
  17. package/dist/__tests__/lib/jobs-service.test.js +56 -2
  18. package/dist/__tests__/lib/jobs-service.test.js.map +1 -1
  19. package/dist/__tests__/views/appearance-chrome.render.test.d.ts +2 -0
  20. package/dist/__tests__/views/appearance-chrome.render.test.d.ts.map +1 -0
  21. package/dist/__tests__/views/appearance-chrome.render.test.js +688 -0
  22. package/dist/__tests__/views/appearance-chrome.render.test.js.map +1 -0
  23. package/dist/__tests__/views/header-footer-form.test.d.ts +2 -0
  24. package/dist/__tests__/views/header-footer-form.test.d.ts.map +1 -0
  25. package/dist/__tests__/views/header-footer-form.test.js +266 -0
  26. package/dist/__tests__/views/header-footer-form.test.js.map +1 -0
  27. package/dist/__tests__/views/jobs-drawer.render.test.js +442 -2
  28. package/dist/__tests__/views/jobs-drawer.render.test.js.map +1 -1
  29. package/dist/__tests__/views/jobs-list.render.test.js +7 -5
  30. package/dist/__tests__/views/jobs-list.render.test.js.map +1 -1
  31. package/dist/__tests__/views/jobs-wizard.render.test.d.ts +2 -0
  32. package/dist/__tests__/views/jobs-wizard.render.test.d.ts.map +1 -0
  33. package/dist/__tests__/views/jobs-wizard.render.test.js +588 -0
  34. package/dist/__tests__/views/jobs-wizard.render.test.js.map +1 -0
  35. package/dist/lib/api.d.ts +2 -0
  36. package/dist/lib/api.d.ts.map +1 -1
  37. package/dist/lib/api.js +8 -1
  38. package/dist/lib/api.js.map +1 -1
  39. package/dist/lib/batch-wizard.d.ts +84 -0
  40. package/dist/lib/batch-wizard.d.ts.map +1 -0
  41. package/dist/lib/batch-wizard.js +122 -0
  42. package/dist/lib/batch-wizard.js.map +1 -0
  43. package/dist/lib/chrome-service.d.ts +69 -0
  44. package/dist/lib/chrome-service.d.ts.map +1 -0
  45. package/dist/lib/chrome-service.js +108 -0
  46. package/dist/lib/chrome-service.js.map +1 -0
  47. package/dist/lib/job-view.d.ts +43 -0
  48. package/dist/lib/job-view.d.ts.map +1 -1
  49. package/dist/lib/job-view.js +57 -1
  50. package/dist/lib/job-view.js.map +1 -1
  51. package/dist/lib/jobs-service.d.ts +19 -1
  52. package/dist/lib/jobs-service.d.ts.map +1 -1
  53. package/dist/lib/jobs-service.js +41 -0
  54. package/dist/lib/jobs-service.js.map +1 -1
  55. package/dist/prenta-admin.css +1 -1
  56. package/dist/views/AppearanceChrome.d.ts +22 -4
  57. package/dist/views/AppearanceChrome.d.ts.map +1 -1
  58. package/dist/views/AppearanceChrome.js +451 -15
  59. package/dist/views/AppearanceChrome.js.map +1 -1
  60. package/dist/views/Jobs/CancelJobDialog.d.ts +7 -1
  61. package/dist/views/Jobs/CancelJobDialog.d.ts.map +1 -1
  62. package/dist/views/Jobs/CancelJobDialog.js +13 -3
  63. package/dist/views/Jobs/CancelJobDialog.js.map +1 -1
  64. package/dist/views/Jobs/DrawerReview.d.ts +7 -0
  65. package/dist/views/Jobs/DrawerReview.d.ts.map +1 -1
  66. package/dist/views/Jobs/DrawerReview.js +1 -1
  67. package/dist/views/Jobs/DrawerReview.js.map +1 -1
  68. package/dist/views/Jobs/JobDrawer.d.ts +23 -3
  69. package/dist/views/Jobs/JobDrawer.d.ts.map +1 -1
  70. package/dist/views/Jobs/JobDrawer.js +30 -5
  71. package/dist/views/Jobs/JobDrawer.js.map +1 -1
  72. package/dist/views/Jobs/JobsTable.d.ts +4 -2
  73. package/dist/views/Jobs/JobsTable.d.ts.map +1 -1
  74. package/dist/views/Jobs/JobsTable.js +6 -5
  75. package/dist/views/Jobs/JobsTable.js.map +1 -1
  76. package/dist/views/Jobs/NewBatchJob.d.ts +34 -0
  77. package/dist/views/Jobs/NewBatchJob.d.ts.map +1 -0
  78. package/dist/views/Jobs/NewBatchJob.js +282 -0
  79. package/dist/views/Jobs/NewBatchJob.js.map +1 -0
  80. package/dist/views/Jobs/WorkerStrip.d.ts +3 -1
  81. package/dist/views/Jobs/WorkerStrip.d.ts.map +1 -1
  82. package/dist/views/Jobs/WorkerStrip.js +2 -2
  83. package/dist/views/Jobs/WorkerStrip.js.map +1 -1
  84. package/dist/views/Jobs/focus.d.ts +6 -0
  85. package/dist/views/Jobs/focus.d.ts.map +1 -1
  86. package/dist/views/Jobs/focus.js +10 -0
  87. package/dist/views/Jobs/focus.js.map +1 -1
  88. package/dist/views/Jobs.d.ts +12 -3
  89. package/dist/views/Jobs.d.ts.map +1 -1
  90. package/dist/views/Jobs.js +109 -31
  91. package/dist/views/Jobs.js.map +1 -1
  92. package/dist/views/header-footer/AnnouncementTab.d.ts +15 -0
  93. package/dist/views/header-footer/AnnouncementTab.d.ts.map +1 -0
  94. package/dist/views/header-footer/AnnouncementTab.js +43 -0
  95. package/dist/views/header-footer/AnnouncementTab.js.map +1 -0
  96. package/dist/views/header-footer/ChromeField.d.ts +25 -0
  97. package/dist/views/header-footer/ChromeField.d.ts.map +1 -0
  98. package/dist/views/header-footer/ChromeField.js +99 -0
  99. package/dist/views/header-footer/ChromeField.js.map +1 -0
  100. package/dist/views/header-footer/ChromePreview.d.ts +31 -0
  101. package/dist/views/header-footer/ChromePreview.d.ts.map +1 -0
  102. package/dist/views/header-footer/ChromePreview.js +195 -0
  103. package/dist/views/header-footer/ChromePreview.js.map +1 -0
  104. package/dist/views/header-footer/DeveloperTab.d.ts +17 -0
  105. package/dist/views/header-footer/DeveloperTab.d.ts.map +1 -0
  106. package/dist/views/header-footer/DeveloperTab.js +98 -0
  107. package/dist/views/header-footer/DeveloperTab.js.map +1 -0
  108. package/dist/views/header-footer/RegionTab.d.ts +27 -0
  109. package/dist/views/header-footer/RegionTab.d.ts.map +1 -0
  110. package/dist/views/header-footer/RegionTab.js +80 -0
  111. package/dist/views/header-footer/RegionTab.js.map +1 -0
  112. package/dist/views/header-footer/chrome-form.d.ts +137 -0
  113. package/dist/views/header-footer/chrome-form.d.ts.map +1 -0
  114. package/dist/views/header-footer/chrome-form.js +518 -0
  115. package/dist/views/header-footer/chrome-form.js.map +1 -0
  116. package/dist/views/header-footer/code-samples.d.ts +30 -0
  117. package/dist/views/header-footer/code-samples.d.ts.map +1 -0
  118. package/dist/views/header-footer/code-samples.js +127 -0
  119. package/dist/views/header-footer/code-samples.js.map +1 -0
  120. package/dist/views/header-footer/controls.d.ts +57 -0
  121. package/dist/views/header-footer/controls.d.ts.map +1 -0
  122. package/dist/views/header-footer/controls.js +43 -0
  123. package/dist/views/header-footer/controls.js.map +1 -0
  124. package/package.json +3 -3
  125. package/src/AdminRoot.tsx +16 -2
  126. package/src/__tests__/lib/batch-wizard.test.ts +199 -0
  127. package/src/__tests__/lib/job-drawer.test.ts +2 -0
  128. package/src/__tests__/lib/job-view.test.ts +123 -2
  129. package/src/__tests__/lib/jobs-service.test.ts +72 -3
  130. package/src/__tests__/views/appearance-chrome.render.test.tsx +818 -0
  131. package/src/__tests__/views/header-footer-form.test.ts +333 -0
  132. package/src/__tests__/views/jobs-drawer.render.test.tsx +508 -2
  133. package/src/__tests__/views/jobs-list.render.test.tsx +8 -5
  134. package/src/__tests__/views/jobs-wizard.render.test.tsx +707 -0
  135. package/src/lib/api.ts +12 -1
  136. package/src/lib/batch-wizard.ts +188 -0
  137. package/src/lib/chrome-service.ts +200 -0
  138. package/src/lib/job-view.ts +95 -1
  139. package/src/lib/jobs-service.ts +56 -1
  140. package/src/styles/prenta-tokens.css +17 -0
  141. package/src/styles/theme.css +15 -0
  142. package/src/views/AppearanceChrome.tsx +704 -46
  143. package/src/views/Jobs/CancelJobDialog.tsx +20 -2
  144. package/src/views/Jobs/DrawerReview.tsx +1 -1
  145. package/src/views/Jobs/JobDrawer.tsx +83 -3
  146. package/src/views/Jobs/JobsTable.tsx +11 -4
  147. package/src/views/Jobs/NewBatchJob.tsx +857 -0
  148. package/src/views/Jobs/WorkerStrip.tsx +16 -11
  149. package/src/views/Jobs/focus.ts +11 -0
  150. package/src/views/Jobs.tsx +163 -49
  151. package/src/views/header-footer/AnnouncementTab.tsx +231 -0
  152. package/src/views/header-footer/ChromeField.tsx +358 -0
  153. package/src/views/header-footer/ChromePreview.tsx +497 -0
  154. package/src/views/header-footer/DeveloperTab.tsx +331 -0
  155. package/src/views/header-footer/RegionTab.tsx +398 -0
  156. package/src/views/header-footer/chrome-form.ts +603 -0
  157. package/src/views/header-footer/code-samples.ts +146 -0
  158. package/src/views/header-footer/controls.tsx +166 -0
  159. package/dist/views/Jobs/placeholders.d.ts +0 -6
  160. package/dist/views/Jobs/placeholders.d.ts.map +0 -1
  161. package/dist/views/Jobs/placeholders.js +0 -20
  162. package/dist/views/Jobs/placeholders.js.map +0 -1
  163. package/src/views/Jobs/placeholders.tsx +0 -66
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import { useState } from 'react'
3
+ import { useRef, useState } from 'react'
4
4
  import * as Dialog from '@radix-ui/react-dialog'
5
5
  import { Button } from '../../components/ui/Button.js'
6
6
  import { jobType, type JobRow } from '../../lib/job-view.js'
@@ -51,19 +51,28 @@ export interface CancelJobDialogProps {
51
51
  * as it is once a cancel goes through, or never had focus.
52
52
  */
53
53
  onFocusFallback: () => void
54
+ /**
55
+ * The dialog has gone and focus is back. A cancel's outcome is reported
56
+ * from here: while the dialog is open, screen readers are kept inside it.
57
+ */
58
+ onClosed?: () => void
54
59
  }
55
60
 
56
61
  /**
57
62
  * The cancel confirmation (jobs-view.jsx `CancelDlg`, JOBS.md "Cancel
58
63
  * dialog"): 460px, radius 14, padding 20. Radix traps focus and closes on Esc.
64
+ * It opens from a row or over the drawer; Esc and the backdrop close only it.
59
65
  */
60
66
  export function CancelJobDialog({
61
67
  job,
62
68
  onClose,
63
69
  onConfirm,
64
70
  onFocusFallback,
71
+ onClosed,
65
72
  }: CancelJobDialogProps) {
66
73
  const [busy, setBusy] = useState(false)
74
+ /** Set before the first await, so a second click can't post a second cancel. */
75
+ const sending = useRef(false)
67
76
  const returnFocus = useReturnFocus(onFocusFallback)
68
77
 
69
78
  return (
@@ -77,11 +86,17 @@ export function CancelJobDialog({
77
86
  <Dialog.Overlay className="bg-scrim fixed inset-0 z-[var(--z-modal)]" />
78
87
  <Dialog.Content
79
88
  {...returnFocus}
89
+ onCloseAutoFocus={(event) => {
90
+ returnFocus.onCloseAutoFocus(event)
91
+ onClosed?.()
92
+ }}
93
+ // Radix makes the rest of the page inert but doesn't say so on the dialog.
94
+ aria-modal="true"
80
95
  className="bg-card border-bdr rounded-14 shadow-shd2 fixed top-1/2 left-1/2 z-[var(--z-modal)] flex w-[min(460px,calc(100%-32px))] -translate-x-1/2 -translate-y-1/2 flex-col gap-3 border p-5 leading-[normal]"
81
96
  >
82
97
  {job ? (
83
98
  <>
84
- <Dialog.Title className="text-txt m-0 text-[length:var(--fs-drawer)] font-bold">
99
+ <Dialog.Title className="text-txt m-0 text-[length:var(--fs-drawer)] leading-[normal] font-bold">
85
100
  Cancel “{job.title}”?
86
101
  </Dialog.Title>
87
102
  <Dialog.Description className="text-body text-sub m-0 leading-[1.55] text-pretty">
@@ -106,10 +121,13 @@ export function CancelJobDialog({
106
121
  busy={busy}
107
122
  leftIcon={<JobIcons.stop size={12} />}
108
123
  onClick={async () => {
124
+ if (sending.current) return
125
+ sending.current = true
109
126
  setBusy(true)
110
127
  try {
111
128
  await onConfirm(job)
112
129
  } finally {
130
+ sending.current = false
113
131
  setBusy(false)
114
132
  }
115
133
  onClose()
@@ -111,7 +111,7 @@ function DiffLabel({ label, children }: { label: string; children?: ReactNode })
111
111
  * A link suggestion's line, as text: the words around the link, the linked
112
112
  * words marked, and where they link. The stored markup is never rendered.
113
113
  */
114
- function LinkLine({ after }: { after: string }) {
114
+ export function LinkLine({ after }: { after: string }) {
115
115
  const parts = linkParts(after)
116
116
  if (!parts) return <>{after}</>
117
117
  return (
@@ -13,7 +13,7 @@ import {
13
13
  drawerTabs,
14
14
  type DrawerTab,
15
15
  } from '../../lib/job-drawer.js'
16
- import { active, view, type JobRow } from '../../lib/job-view.js'
16
+ import { active, retryPoint, view, type JobActions, type JobRow } from '../../lib/job-view.js'
17
17
  import {
18
18
  approveAllReviewItems,
19
19
  decideReviewItem,
@@ -29,7 +29,7 @@ import { DrawerActivity } from './DrawerActivity.js'
29
29
  import { DrawerOverview } from './DrawerOverview.js'
30
30
  import { DrawerResults } from './DrawerResults.js'
31
31
  import { DrawerReview, type ReviewItemState } from './DrawerReview.js'
32
- import { focusJobRow, useReturnFocus } from './focus.js'
32
+ import { focusJobDrawer, focusJobRow, useReturnFocus } from './focus.js'
33
33
  import { JobIcons } from './icons.js'
34
34
  import type { JobDrawerTab } from './JobsTable.js'
35
35
  import { useJobDetail, useJobEvents } from './useJobDetail.js'
@@ -71,6 +71,12 @@ interface FocusMove {
71
71
  fallback?: boolean
72
72
  }
73
73
 
74
+ /** An outcome the Jobs screen reported, for the drawer's live region; `n` makes a repeat new. */
75
+ export interface DrawerNotice {
76
+ text: string
77
+ n: number
78
+ }
79
+
74
80
  export interface JobDrawerProps {
75
81
  /** The list's copy of the job, read again every 5 seconds while Live is on. */
76
82
  job: Job
@@ -81,6 +87,20 @@ export interface JobDrawerProps {
81
87
  /** When the list was read, on the server's clock. Relative times count from it. */
82
88
  now: number
83
89
  worker: JobWorkerStatus | null
90
+ /** The footer's Cancel job, "Retry from document {n+1}" and "Retry now", for this viewer. */
91
+ actions: JobActions
92
+ /** A retry of this job is out. */
93
+ retrying: boolean
94
+ /**
95
+ * The screen's latest outcome (a cancel, a retry, a reused Run now). Radix
96
+ * hides the toaster from screen readers while the drawer is open, so the
97
+ * drawer announces it too.
98
+ */
99
+ notice: DrawerNotice | null
100
+ /** Cancel job: opens the cancel dialog over the drawer. */
101
+ onCancel: () => void
102
+ /** Retry from where it stopped, or retry now while it waits out a backoff. Resolves once answered. */
103
+ onRetry: (now: boolean) => Promise<void>
84
104
  onClose: () => void
85
105
  /** Read the list again: a decision changes the job's review counts. */
86
106
  onReload: () => void
@@ -112,7 +132,8 @@ async function copyText(text: string): Promise<boolean> {
112
132
  * It reads `GET /jobs/:id` (results, the full error) and, per tab, the
113
133
  * events and the review changes, each again only when the list's copy of the
114
134
  * job changes. The Review badge and footer use the job's own counts, never
115
- * the number of changes loaded. Cancel and retry in the footer are P-19.
135
+ * the number of changes loaded. The footer's Cancel job and retries (P-19)
136
+ * use the list's handlers, so they act and report the same way from here.
116
137
  */
117
138
  export function JobDrawer({
118
139
  job,
@@ -120,6 +141,11 @@ export function JobDrawer({
120
141
  tab,
121
142
  now,
122
143
  worker,
144
+ actions,
145
+ retrying,
146
+ notice,
147
+ onCancel,
148
+ onRetry,
123
149
  onClose,
124
150
  onReload,
125
151
  onNavigate,
@@ -162,6 +188,11 @@ export function JobDrawer({
162
188
  [],
163
189
  )
164
190
 
191
+ // The screen clears its notice when a drawer opens or closes, so one here is this drawer's.
192
+ useEffect(() => {
193
+ if (notice) announce(notice.text)
194
+ }, [notice, announce])
195
+
165
196
  const [itemState, setItemState] = useState<Record<string, ReviewItemState>>({})
166
197
  const [drafts, setDrafts] = useState<Record<string, string>>({})
167
198
  /** Documents known to be published or not, from decisions; keyed by document id. */
@@ -171,6 +202,8 @@ export function JobDrawer({
171
202
  /** Bumped so the focus effect runs for a move asked for after an await. */
172
203
  const [, setFocusTick] = useState(0)
173
204
  const afterApproveAll = useRef(false)
205
+ /** A footer retry was answered; its button may have gone with the failure. */
206
+ const afterRetry = useRef(false)
174
207
  /** "Approve all" ignores activations before this time (`APPROVE_ALL_ARM_MS`). */
175
208
  const approveAllFrom = useRef(0)
176
209
  /** A key held on the footer button is repeating, and each repeat clicks it. */
@@ -248,6 +281,24 @@ export function JobDrawer({
248
281
  }
249
282
  })
250
283
 
284
+ // A retry's button goes away with the failure it answered. Focus lost with
285
+ // it (Radix puts it on the dialog itself) moves to the panel, which now
286
+ // shows the job queued again.
287
+ useEffect(() => {
288
+ if (!afterRetry.current) return
289
+ afterRetry.current = false
290
+ const focused = document.activeElement
291
+ if (focused && focused !== document.body && focused !== content.current && focused.isConnected)
292
+ return
293
+ focusJobDrawer()
294
+ })
295
+
296
+ async function retryHere(retryNow: boolean) {
297
+ await onRetry(retryNow)
298
+ afterRetry.current = true
299
+ setFocusTick((tick) => tick + 1)
300
+ }
301
+
251
302
  // When approve-all leaves nothing to review its button goes away; focus
252
303
  // moves to the changes rather than staying on the dialog itself.
253
304
  useEffect(() => {
@@ -514,6 +565,35 @@ export function JobDrawer({
514
565
  </UnderlineTabPanel>
515
566
 
516
567
  <div className="border-bdr mobile:pb-[calc(12px+env(safe-area-inset-bottom,0px))] flex flex-wrap items-center gap-2 border-t px-[18px] py-3">
568
+ {actions.cancel ? (
569
+ <Button
570
+ variant="danger"
571
+ aria-haspopup="dialog"
572
+ leftIcon={<JobIcons.stop size={12} />}
573
+ onClick={onCancel}
574
+ >
575
+ Cancel job
576
+ </Button>
577
+ ) : null}
578
+ {actions.retry ? (
579
+ <BusyButton
580
+ leftIcon={<JobIcons.retry size={13} />}
581
+ busy={retrying}
582
+ onClick={() => void retryHere(false)}
583
+ >
584
+ Retry from {retryPoint(row)}
585
+ </BusyButton>
586
+ ) : null}
587
+ {actions.retryNow ? (
588
+ <BusyButton
589
+ variant="secondary"
590
+ leftIcon={<JobIcons.retry size={13} />}
591
+ busy={retrying}
592
+ onClick={() => void retryHere(true)}
593
+ >
594
+ Retry now
595
+ </BusyButton>
596
+ ) : null}
517
597
  <span className="flex-1" />
518
598
  {/* One button for both, so it keeps focus when it turns into Approve all. */}
519
599
  {canReview && counts.pending > 0 ? (
@@ -5,11 +5,12 @@ import { Button } from '../../components/ui/Button.js'
5
5
  import { Card } from '../../components/ui/Card.js'
6
6
  import { Skeleton } from '../../components/ui/Skeleton.js'
7
7
  import {
8
- active,
9
8
  hasReviewAction,
9
+ jobActions,
10
10
  view,
11
11
  type JobFilter,
12
12
  type JobRow as JobRowModel,
13
+ type JobViewer,
13
14
  } from '../../lib/job-view.js'
14
15
  import { Bar, Pill, TypeTile } from './atoms.js'
15
16
  import { BusyButton } from './BusyButton.js'
@@ -53,6 +54,7 @@ interface RowProps {
53
54
  j: JobRowModel
54
55
  selected: boolean
55
56
  retrying: boolean
57
+ viewer: JobViewer
56
58
  onOpen: (id: string, tab: JobDrawerTab) => void
57
59
  onCancel: (row: JobRowModel) => void
58
60
  onRetry: (row: JobRowModel) => void
@@ -63,10 +65,11 @@ interface RowProps {
63
65
  * the button and its `::after` covers the row, so the Review, Cancel and Retry
64
66
  * buttons aren't nested inside another control. They sit above that layer.
65
67
  */
66
- function JobRowItem({ j, selected, retrying, onOpen, onCancel, onRetry }: RowProps) {
68
+ function JobRowItem({ j, selected, retrying, viewer, onOpen, onCancel, onRetry }: RowProps) {
67
69
  const v = view(j)
68
70
  const statusId = `job-status-${j.id}`
69
71
  const review = hasReviewAction(j)
72
+ const actions = jobActions(j, viewer)
70
73
  return (
71
74
  <li
72
75
  className={`${ROW_GRID} border-bdr relative border-t px-4 py-3 ${
@@ -114,7 +117,7 @@ function JobRowItem({ j, selected, retrying, onOpen, onCancel, onRetry }: RowPro
114
117
  Review
115
118
  </Button>
116
119
  ) : null}
117
- {active(j) && !j.cancelling ? (
120
+ {actions.cancel ? (
118
121
  <Button
119
122
  variant="icon"
120
123
  size="icon"
@@ -126,7 +129,7 @@ function JobRowItem({ j, selected, retrying, onOpen, onCancel, onRetry }: RowPro
126
129
  <JobIcons.stop size={13} />
127
130
  </Button>
128
131
  ) : null}
129
- {j.state === 'dead' ? (
132
+ {actions.retry ? (
130
133
  <BusyButton
131
134
  variant="icon"
132
135
  size="icon"
@@ -162,6 +165,8 @@ export interface JobsTableProps {
162
165
  selectedId: string | null
163
166
  /** The job whose retry request is out. */
164
167
  retryingId: string | null
168
+ /** Who's looking: Cancel and Retry show only to someone who may use them. */
169
+ viewer: JobViewer
165
170
  status: 'loading' | 'ready' | 'error'
166
171
  error: string | null
167
172
  onOpen: (id: string, tab: JobDrawerTab) => void
@@ -176,6 +181,7 @@ export function JobsTable({
176
181
  filter,
177
182
  selectedId,
178
183
  retryingId,
184
+ viewer,
179
185
  status,
180
186
  error,
181
187
  onOpen,
@@ -238,6 +244,7 @@ export function JobsTable({
238
244
  j={j}
239
245
  selected={selectedId === j.id}
240
246
  retrying={retryingId === j.id}
247
+ viewer={viewer}
241
248
  onOpen={onOpen}
242
249
  onCancel={onCancel}
243
250
  onRetry={onRetry}