@linxin666/dsh-client-ui-task-board 0.4.4 → 0.4.6

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 (120) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +59 -20
  3. package/README.zh.md +59 -20
  4. package/lib/client.js +6208 -624
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +4774 -493
  7. package/lib/types/client/PluginSettingsCard.d.ts +7 -0
  8. package/lib/types/client/TaskBoardSettingsCard.d.ts +31 -3
  9. package/lib/types/client/board/ClampedMarkdown.d.ts +3 -0
  10. package/lib/types/client/board/ConfirmDialog.d.ts +4 -1
  11. package/lib/types/client/board/EditTaskModal.d.ts +5 -2
  12. package/lib/types/client/board/LinkSubtaskModal.d.ts +5 -2
  13. package/lib/types/client/board/NewTaskModal.d.ts +4 -1
  14. package/lib/types/client/board/TagManagerModal.d.ts +26 -0
  15. package/lib/types/client/board/TaskBoard.d.ts +10 -1
  16. package/lib/types/client/board/TaskCard.d.ts +14 -1
  17. package/lib/types/client/board/TaskDetail.d.ts +3 -1
  18. package/lib/types/client/board/TaskForm.d.ts +4 -1
  19. package/lib/types/client/board/VerificationReport.d.ts +23 -0
  20. package/lib/types/client/board/card-view.d.ts +113 -0
  21. package/lib/types/client/board/icons.d.ts +32 -0
  22. package/lib/types/client/board/overlay.d.ts +69 -0
  23. package/lib/types/client/board/status-key.d.ts +15 -0
  24. package/lib/types/client/board/task-markdown.d.ts +19 -0
  25. package/lib/types/client/index.d.ts +23 -5
  26. package/lib/types/client/locales.d.ts +168 -1
  27. package/lib/types/client/native-panel.d.ts +6 -20
  28. package/lib/types/client/plugin-card-seat.d.ts +5 -0
  29. package/lib/types/client/schedule-zone.d.ts +50 -0
  30. package/lib/types/client/seats.d.ts +18 -0
  31. package/lib/types/client/service.d.ts +32 -0
  32. package/lib/types/client/settings-form.d.ts +16 -0
  33. package/lib/types/core/controller.d.ts +46 -11
  34. package/lib/types/core/extension.d.ts +283 -0
  35. package/lib/types/core/handover.d.ts +13 -1
  36. package/lib/types/core/issue-form.d.ts +16 -0
  37. package/lib/types/core/poll-cadence.d.ts +31 -0
  38. package/lib/types/core/tasks.d.ts +228 -17
  39. package/lib/types/core/use-cases/task-schedule.d.ts +68 -10
  40. package/lib/types/core/use-cases/task-tag.d.ts +48 -0
  41. package/lib/types/core/use-cases/task-update.d.ts +5 -3
  42. package/lib/types/core/verification-budget.d.ts +52 -0
  43. package/lib/types/core/verification.d.ts +753 -0
  44. package/lib/types/core/workspace-target.d.ts +41 -0
  45. package/lib/types/host/agent-tools.d.ts +1 -1
  46. package/lib/types/host/extension-registry.d.ts +111 -0
  47. package/lib/types/host/llm-dispatch.d.ts +63 -0
  48. package/lib/types/host/verification-gate.d.ts +95 -0
  49. package/lib/types/host/verification-runner.d.ts +139 -0
  50. package/lib/types/host/workspace-evidence.d.ts +53 -0
  51. package/lib/types/host-ledger.d.ts +133 -14
  52. package/lib/types/host-runner.d.ts +55 -10
  53. package/lib/types/host-service.d.ts +205 -11
  54. package/lib/types/index.d.ts +134 -26
  55. package/lib/types/protocol.d.ts +92 -10
  56. package/lib/types/tool-surface.d.ts +71 -0
  57. package/package.json +28 -31
  58. package/src/client/PluginSettingsCard.tsx +9 -0
  59. package/src/client/TaskBoardSettingsCard.tsx +378 -66
  60. package/src/client/board/ClampedMarkdown.tsx +69 -0
  61. package/src/client/board/ConfirmDialog.tsx +7 -3
  62. package/src/client/board/EditTaskModal.tsx +5 -2
  63. package/src/client/board/LinkSubtaskModal.tsx +12 -7
  64. package/src/client/board/NewTaskModal.tsx +168 -21
  65. package/src/client/board/TagManagerModal.tsx +235 -0
  66. package/src/client/board/TaskBoard.tsx +259 -103
  67. package/src/client/board/TaskCard.tsx +214 -81
  68. package/src/client/board/TaskDetail.tsx +289 -43
  69. package/src/client/board/TaskForm.tsx +13 -3
  70. package/src/client/board/VerificationReport.tsx +322 -0
  71. package/src/client/board/card-view.ts +220 -0
  72. package/src/client/board/icons.tsx +89 -0
  73. package/src/client/board/overlay.tsx +189 -0
  74. package/src/client/board/status-key.ts +30 -0
  75. package/src/client/board/task-markdown.module.css +137 -0
  76. package/src/client/board/task-markdown.tsx +106 -0
  77. package/src/client/board-settings.module.css +66 -0
  78. package/src/client/board.module.css +488 -63
  79. package/src/client/index.ts +82 -51
  80. package/src/client/locales.ts +343 -6
  81. package/src/client/native-panel.tsx +18 -2
  82. package/src/client/plugin-card-seat.ts +7 -0
  83. package/src/client/schedule-zone.ts +100 -2
  84. package/src/client/seats.tsx +58 -0
  85. package/src/client/service.ts +94 -0
  86. package/src/client/settings-card.module.css +31 -5
  87. package/src/client/settings-form.ts +27 -1
  88. package/src/core/controller.ts +105 -10
  89. package/src/core/extension.ts +376 -0
  90. package/src/core/freeze-snapshot.ts +2 -2
  91. package/src/core/handover.ts +16 -1
  92. package/src/core/issue-form.ts +19 -0
  93. package/src/core/poll-cadence.ts +41 -0
  94. package/src/core/store.ts +80 -15
  95. package/src/core/tasks.ts +293 -45
  96. package/src/core/use-cases/task-create.ts +30 -9
  97. package/src/core/use-cases/task-parent.ts +3 -2
  98. package/src/core/use-cases/task-schedule.ts +190 -25
  99. package/src/core/use-cases/task-tag.ts +110 -0
  100. package/src/core/use-cases/task-update.ts +13 -7
  101. package/src/core/verification-budget.ts +115 -0
  102. package/src/core/verification.ts +1594 -0
  103. package/src/core/workspace-target.ts +59 -0
  104. package/src/host/agent-tools.ts +125 -38
  105. package/src/host/extension-registry.ts +509 -0
  106. package/src/host/llm-dispatch.ts +87 -0
  107. package/src/host/verification-gate.ts +487 -0
  108. package/src/host/verification-runner.ts +702 -0
  109. package/src/host/workspace-evidence.ts +176 -0
  110. package/src/host-ai.ts +15 -9
  111. package/src/host-ledger.ts +468 -71
  112. package/src/host-routes.ts +15 -2
  113. package/src/host-runner.ts +106 -24
  114. package/src/host-service.ts +546 -40
  115. package/src/index.ts +305 -21
  116. package/src/protocol.ts +226 -28
  117. package/src/tool-surface.ts +95 -0
  118. package/lib/invariant.js +0 -6
  119. package/lib/types/invariant.d.ts +0 -3
  120. package/src/invariant.ts +0 -4
@@ -1,17 +1,31 @@
1
1
  /**
2
- * Task card: the board's column item. Clicking opens the task detail — it
3
- * never executes anything directly (detail holds the Run button).
2
+ * Task card: the board's column item. The stretched open control opens the
3
+ * task detail — it never executes anything directly (detail holds the Run
4
+ * button).
5
+ *
6
+ * The card is an `article` with one stretched `button` rather than a button
7
+ * itself: interactive content inside a button is invalid HTML, and the card
8
+ * carries its own quick actions (open the execution session, a provider's
9
+ * link such as the tracker issue). The article owns the pointer click and
10
+ * ignores clicks that land on a quick action; the stretched open button is the
11
+ * keyboard and accessibility target, and its click bubbles into the same
12
+ * handler, so the detail opens exactly once either way.
4
13
  *
5
14
  * Memoized: the card re-renders only when its own task record changes, so a
6
15
  * status/filter update on one card (or scrolling) never re-renders every
7
- * card on the board. The per-card onClick is built with a stable task reference
8
- * by the board, so the memo boundary is effective.
16
+ * card on the board. The per-card callbacks are built with a stable task
17
+ * reference by the board, so the memo boundary is effective.
9
18
  */
10
- import { memo } from 'react'
19
+ import { memo, useMemo } from 'react'
11
20
  import type { TaskRecord } from '../../core/tasks.ts'
12
- import { executionLabel, tagTone } from '../../core/tasks.ts'
21
+ import { executionLabel, hasOpenExecution, tagTone } from '../../core/tasks.ts'
13
22
  import { t } from '../locales.ts'
23
+ import { useTaskBoardSeats } from '../seats.tsx'
24
+ import { cardOutcome, cardTimeline, RESULT_KEY, splitTitleKind } from './card-view.ts'
25
+ import { IconClock, IconSession } from './icons.tsx'
26
+ import { verificationRunningKey } from './status-key.ts'
14
27
  import css from '../board.module.css'
28
+ import { markdownExcerpt, markdownToPlainText } from './task-markdown.tsx'
15
29
 
16
30
  /**
17
31
  * Built formatters, keyed by time zone ('' = the browser's own). Constructing
@@ -56,11 +70,43 @@ export function formatTime(ms: number, timeZone?: string): string {
56
70
  return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
57
71
  }
58
72
 
73
+ /** Cached YYYY-MM-DD formatters, keyed by time zone ('' = the browser's own). */
74
+ const CARD_DATE_FORMATS = new Map<string, Intl.DateTimeFormat>()
75
+
76
+ /**
77
+ * Card time label: relative within a day, otherwise the calendar date read in
78
+ * the Host time zone (the same zone the tooltip and the detail view use), so a
79
+ * card never shows a browser-local date beside a Host-zone tooltip.
80
+ * @param ms - the instant.
81
+ * @param timeZone - Host IANA zone; absent uses the browser zone.
82
+ * @param now - the current instant.
83
+ */
84
+ export function formatCardTime(ms: number, timeZone?: string, now: number = Date.now()): string {
85
+ const minutes = Math.floor((now - ms) / 60000)
86
+ if (minutes < 1) return t('time.justNow')
87
+ if (minutes < 60) return `${minutes}m`
88
+ if (minutes < 60 * 24) return `${Math.floor(minutes / 60)}h`
89
+ const key = timeZone ?? ''
90
+ try {
91
+ let format = CARD_DATE_FORMATS.get(key)
92
+ if (format === undefined) {
93
+ format = new Intl.DateTimeFormat('en-CA', { year: 'numeric', month: '2-digit', day: '2-digit', ...(timeZone === undefined ? {} : { timeZone }) })
94
+ CARD_DATE_FORMATS.set(key, format)
95
+ }
96
+ return format.format(new Date(ms))
97
+ } catch {
98
+ // An unsupported zone id: never cached; fall back to the UTC calendar date.
99
+ return new Date(ms).toISOString().slice(0, 10)
100
+ }
101
+ }
102
+
59
103
  function TaskCardInner({
60
104
  task,
61
105
  pending,
62
106
  timeZone,
63
107
  onClick,
108
+ onOpenSession,
109
+ compact = false,
64
110
  subtaskCount = 0,
65
111
  isSubtask = false,
66
112
  subtasksDone = 0,
@@ -71,6 +117,10 @@ function TaskCardInner({
71
117
  pending: boolean
72
118
  timeZone?: string
73
119
  onClick: () => void
120
+ /** Open an execution session; absent renders the session mark inert. */
121
+ onOpenSession?: (sessionId: string) => void
122
+ /** Single-line density used by long settled columns. */
123
+ compact?: boolean
74
124
  /** Direct subtasks of this card, shown as a badge (0 hides it). */
75
125
  subtaskCount?: number
76
126
  /** Whether this card is itself a subtask. */
@@ -82,16 +132,70 @@ function TaskCardInner({
82
132
  /** Direct subtasks that failed, for the roll-up badge. */
83
133
  subtasksFailed?: number
84
134
  }) {
85
- const latest = task.executions[task.executions.length - 1]
135
+ const { cardDecoration } = useTaskBoardSeats()
86
136
  const runs = task.executions.length
137
+ const summary = useMemo(() => markdownToPlainText(task.title) || task.title, [task.title])
138
+ const title = useMemo(() => splitTitleKind(summary), [summary])
139
+ const excerpt = useMemo(() => (compact ? '' : markdownExcerpt(task.description)), [task.description, compact])
87
140
  const archived = task.archivedAt !== undefined
88
- const isDraggable = !archived && task.status !== 'running' && !pending
141
+ // The lock is an open execution, not the column: a card parked in 'running'
142
+ // by hand stays draggable, and an executing card never is.
143
+ const busy = hasOpenExecution(task)
144
+ const isDraggable = !archived && !busy && !pending
145
+ const outcome = cardOutcome(task, archived)
146
+ const latest = outcome.latest
147
+ // Forced acceptance owns the running label.
148
+ const runningKey = verificationRunningKey(latest?.verification)
149
+ const timeline = cardTimeline(task, archived)
150
+ const resultText = latest?.result !== undefined ? t(RESULT_KEY[latest.result]) : ''
151
+ const sessionId = latest?.sessionId
152
+ const tagNames = (task.tags ?? []).map(tag => tag.name)
89
153
 
90
- return (
154
+ const ariaLabel = [
155
+ title.kind === undefined ? summary : `${title.kind}: ${title.text}`,
156
+ !archived && pending ? t('board.pending') : '',
157
+ !archived && busy ? t(runningKey ?? 'detail.result.running') : '',
158
+ !archived && !busy && resultText !== '' ? resultText : '',
159
+ outcome.declared ? t('card.declared') : '',
160
+ subtasksFailed > 0 ? t('card.subtasksFailed', { count: String(subtasksFailed) }) : '',
161
+ tagNames.length > 0 ? t('card.tagsLabel', { tags: tagNames.join(', ') }) : '',
162
+ ].filter(Boolean).join(' · ')
163
+ // The tooltip names what the clamped surface cannot show — the full title and
164
+ // the absolute instant — not the whole excerpt again.
165
+ const tooltip = [
166
+ summary,
167
+ t(timeline.key, { time: formatHostTimestamp(timeline.at, timeZone) }),
168
+ outcome.declared ? t('card.declaredHint') : '',
169
+ ].filter(Boolean).join('\n')
170
+
171
+ const sessionMark = sessionId !== undefined && (onOpenSession !== undefined ? (
91
172
  <button
92
173
  type="button"
174
+ className={`${css.cardSession} ${css.cardAction}`}
175
+ data-dsh-part="card-session"
176
+ title={t('card.openSession')}
177
+ aria-label={t('card.openSession')}
178
+ onClick={() => { onOpenSession(sessionId) }}
179
+ >
180
+ <IconSession size={14} />
181
+ </button>
182
+ ) : (
183
+ <span className={css.cardSession} title={sessionId}>
184
+ <IconSession size={12} />
185
+ </span>
186
+ ))
187
+ const decoration = <span className={css.cardDecoration}>{cardDecoration({ task })}</span>
188
+ const timeLabel = <span className={css.cardTime} data-dsh-part="card-time">{t(timeline.key, { time: formatCardTime(timeline.at, timeZone) })}</span>
189
+ const kindBadge = title.kind !== undefined && (
190
+ <span className={css.cardKind} data-dsh-part="card-kind">{title.kind}</span>
191
+ )
192
+
193
+ return (
194
+ <article
93
195
  className={css.card}
94
196
  data-status={archived ? 'archived' : task.status}
197
+ data-tone={outcome.tone}
198
+ data-compact={compact || undefined}
95
199
  data-dsh-part="card"
96
200
  data-pending={pending || undefined}
97
201
  draggable={isDraggable}
@@ -99,82 +203,111 @@ function TaskCardInner({
99
203
  event.dataTransfer.setData('text/plain', task.id)
100
204
  event.dataTransfer.effectAllowed = 'move'
101
205
  } : undefined}
102
- onClick={onClick}
103
- title={task.description !== '' ? task.description : task.title}
206
+ title={tooltip}
207
+ onClick={(event) => {
208
+ // A quick action (session, provider link) handles its own click.
209
+ const target = event.target as Element
210
+ if (target.closest('a, button:not([data-dsh-part="card-open"])') !== null) return
211
+ onClick()
212
+ }}
104
213
  >
105
- <span className={css.cardTitle}>{task.title}</span>
106
- {task.tags !== undefined && task.tags.length > 0 && (
107
- <span className={css.cardTags}>
108
- {task.tags.map(tag => (
109
- <span
110
- key={tag.name}
111
- className={css.cardTag}
112
- data-tag-tone={tagTone(tag.name)}
113
- data-dsh-part="tag-badge"
114
- data-tag-hint={tag.promptPrefix === undefined ? undefined : tag.promptPrefix}
115
- title={tag.promptPrefix === undefined ? tag.name : tag.promptPrefix}
116
- >
117
- {tag.name}
118
- </span>
119
- ))}
120
- </span>
121
- )}
122
- {task.description !== '' && <span className={css.cardExcerpt}>{task.description}</span>}
123
- <span className={css.cardMeta}>
124
- {isSubtask && (
125
- <span className={css.cardSubtask} data-dsh-part="subtask-badge">{t('card.subtask')}</span>
126
- )}
127
- {subtaskCount > 0 && (
128
- // A board that hides subtask cards still has to report them: the badge
129
- // rolls up the direct children and takes the tone of the worst state,
130
- // so a hidden failing tree cannot look green.
131
- <span
132
- className={css.cardSubtask}
133
- data-dsh-part="subtask-count"
134
- data-tone={subtasksFailed > 0 ? 'failed' : subtasksRunning > 0 ? 'running' : subtasksDone === subtaskCount ? 'done' : undefined}
135
- title={t('card.subtasksBreakdown', {
136
- total: String(subtaskCount),
137
- done: String(subtasksDone),
138
- running: String(subtasksRunning),
139
- failed: String(subtasksFailed),
140
- })}
141
- >
142
- {t('card.subtasks', { count: String(subtaskCount) })}
143
- {subtasksFailed > 0 ? ' · ' + t('card.subtasksFailed', { count: String(subtasksFailed) }) : ''}
144
- {subtasksFailed === 0 && subtasksRunning > 0 ? ' · ' + t('card.subtasksRunning', { count: String(subtasksRunning) }) : ''}
145
- </span>
146
- )}
147
- <span className={css.cardTime}>{t('board.updated')} {formatTime(task.updatedAt)}</span>
148
- {task.freeze !== undefined && (
149
- <span className={css.cardSchedule} title={task.freeze.goal}>{t('card.frozen')}</span>
150
- )}
151
- {!archived && task.schedule?.enabled === true && (
152
- <span
153
- className={css.cardSchedule}
154
- title={task.schedule.nextRunAt !== undefined
155
- ? `${t('card.scheduled')} · ${formatHostTimestamp(task.schedule.nextRunAt, timeZone)}`
156
- : t('card.scheduled')}
157
- >
158
- {t('card.scheduled')}
159
- </span>
160
- )}
161
- {latest !== undefined && (
162
- <span className={css.cardRun} data-result={archived ? undefined : latest.result}>
163
- {runs} {t('board.runs')}
164
- </span>
165
- )}
166
- {latest?.sessionId !== undefined && (
167
- <span className={css.cardSession} title={latest.sessionId}>⌁</span>
168
- )}
169
- {!archived && (task.status === 'running' || pending) && <span className={css.cardSpinner} aria-hidden="true" />}
214
+ <button type="button" className={css.cardOpen} data-dsh-part="card-open" aria-label={ariaLabel} />
215
+ <span className={css.cardTitleRow}>
216
+ {kindBadge}
217
+ <span className={css.cardTitle} data-dsh-part="card-title">{title.text}</span>
170
218
  </span>
171
- {!archived && pending && <span className={css.cardRunningLabel}>{t('board.pending')}…</span>}
172
- {!archived && latest !== undefined && executionLabel(latest) === 'running' && (
173
- <span className={css.cardRunningLabel}>
174
- {latest.ownResult === undefined ? t('detail.result.running') : t('detail.subtasks.waiting')}…
219
+ {compact ? (
220
+ <span className={css.cardMeta}>
221
+ {decoration}
222
+ {timeLabel}
223
+ {!archived && (busy || pending) && <span className={css.cardSpinner} aria-hidden="true" />}
175
224
  </span>
225
+ ) : (
226
+ <>
227
+ {task.tags !== undefined && task.tags.length > 0 && (
228
+ <span className={css.cardTags}>
229
+ {task.tags.map(tag => (
230
+ <span
231
+ key={tag.name}
232
+ className={css.cardTag}
233
+ data-tag-tone={tagTone(tag.name)}
234
+ data-dsh-part="tag-badge"
235
+ data-tag-hint={tag.promptPrefix === undefined ? undefined : tag.promptPrefix}
236
+ title={tag.promptPrefix === undefined ? tag.name : tag.promptPrefix}
237
+ >
238
+ {tag.name}
239
+ </span>
240
+ ))}
241
+ </span>
242
+ )}
243
+ {excerpt !== '' && <span className={css.cardExcerpt} data-dsh-part="card-excerpt">{excerpt}</span>}
244
+ <span className={css.cardMeta}>
245
+ {decoration}
246
+ {isSubtask && (
247
+ <span className={css.cardSubtask} data-dsh-part="subtask-badge">{t('card.subtask')}</span>
248
+ )}
249
+ {subtaskCount > 0 && (
250
+ // A board that hides subtask cards still has to report them: the badge
251
+ // rolls up the direct children and takes the tone of the worst state,
252
+ // so a hidden failing tree cannot look green.
253
+ <span
254
+ className={css.cardSubtask}
255
+ data-dsh-part="subtask-count"
256
+ data-tone={subtasksFailed > 0 ? 'failed' : subtasksRunning > 0 ? 'running' : subtasksDone === subtaskCount ? 'done' : undefined}
257
+ title={t('card.subtasksBreakdown', {
258
+ total: String(subtaskCount),
259
+ done: String(subtasksDone),
260
+ running: String(subtasksRunning),
261
+ failed: String(subtasksFailed),
262
+ })}
263
+ >
264
+ {t('card.subtasks', { count: String(subtaskCount) })}
265
+ {subtasksFailed > 0 ? ' · ' + t('card.subtasksFailed', { count: String(subtasksFailed) }) : ''}
266
+ {subtasksFailed === 0 && subtasksRunning > 0 ? ' · ' + t('card.subtasksRunning', { count: String(subtasksRunning) }) : ''}
267
+ </span>
268
+ )}
269
+ {timeLabel}
270
+ {task.freeze !== undefined && (
271
+ <span className={css.cardSchedule} title={task.freeze.goal}>{t('card.frozen')}</span>
272
+ )}
273
+ {!archived && task.schedule?.enabled === true && (
274
+ <span
275
+ className={css.cardSchedule}
276
+ title={task.schedule.nextRunAt !== undefined
277
+ ? `${t(task.schedule.mode === 'once' ? 'card.scheduledOnce' : 'card.scheduled')} · ${formatHostTimestamp(task.schedule.nextRunAt, timeZone)}`
278
+ : t(task.schedule.mode === 'once' ? 'card.scheduledOnce' : 'card.scheduled')}
279
+ >
280
+ <IconClock size={12} />
281
+ {t(task.schedule.mode === 'once' ? 'card.scheduledOnce' : 'card.scheduled')}
282
+ </span>
283
+ )}
284
+ {latest !== undefined && (
285
+ <span
286
+ className={css.cardRun}
287
+ data-dsh-part="card-run"
288
+ data-result={archived || latest.result === undefined ? undefined : latest.result}
289
+ data-declared={outcome.declared || undefined}
290
+ title={outcome.declared ? t('card.declaredHint') : undefined}
291
+ >
292
+ {!busy && resultText !== '' ? `${resultText} · ` : ''}
293
+ {runs} {t('board.runs')}
294
+ {outcome.declared ? ` · ${t('card.declared')}` : ''}
295
+ </span>
296
+ )}
297
+ {sessionMark}
298
+ {!archived && (busy || pending) && <span className={css.cardSpinner} aria-hidden="true" />}
299
+ </span>
300
+ {!archived && pending && <span className={css.cardRunningLabel}>{t('board.pending')}…</span>}
301
+ {!archived && latest !== undefined && executionLabel(latest) === 'running' && (
302
+ <span className={css.cardRunningLabel}>
303
+ {runningKey !== undefined
304
+ ? t(runningKey)
305
+ : latest.ownResult === undefined ? t('detail.result.running') : t('detail.subtasks.waiting')}…
306
+ </span>
307
+ )}
308
+ </>
176
309
  )}
177
- </button>
310
+ </article>
178
311
  )
179
312
  }
180
313