dsh-agent-board 1.7.6 → 1.7.8
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.
- package/README.md +42 -4
- package/index.mjs +4 -2
- package/lib/client/board-list.js +35 -16
- package/lib/client/dashboard.js +15 -9
- package/lib/client/kernel.js +41 -9
- package/lib/client/task-detail.js +81 -1
- package/lib/client.js +172 -35
- package/lib/core.mjs +124 -1
- package/lib/dispatch.mjs +43 -7
- package/lib/health.mjs +22 -0
- package/lib/notify.mjs +23 -1
- package/lib/rpc.mjs +143 -15
- package/lib/store.mjs +139 -37
- package/package.json +1 -1
- package/test/archive-detail.test.mjs +43 -0
- package/test/core.test.mjs +204 -17
- package/test/headless-smoke.test.mjs +25 -0
- package/test/helpers/mock-ctx.mjs +17 -3
- package/test/review-hints.test.mjs +170 -0
- package/test/scoreboard-trends-client.test.mjs +21 -0
- package/test/store-data-safety.test.mjs +217 -0
- package/test/verify-timing.test.mjs +292 -0
package/lib/client.js
CHANGED
|
@@ -207,6 +207,11 @@ function apply(ctx) {
|
|
|
207
207
|
['circle', { cx: 12, cy: 12, r: 10 }],
|
|
208
208
|
['rect', { width: 6, height: 6, x: 9, y: 9 }]
|
|
209
209
|
],
|
|
210
|
+
// 取消入口图标(详情页「取消」按钮;语义=作废不派发,与删除 trash-2 区分)
|
|
211
|
+
'ban': [
|
|
212
|
+
['circle', { cx: 12, cy: 12, r: 10 }],
|
|
213
|
+
['path', { d: 'm4.9 4.9 14.2 14.2' }]
|
|
214
|
+
],
|
|
210
215
|
'plus': [
|
|
211
216
|
['path', { d: 'M5 12h14' }],
|
|
212
217
|
['path', { d: 'M12 5v14' }]
|
|
@@ -242,7 +247,7 @@ function apply(ctx) {
|
|
|
242
247
|
blocked: { title: '阻塞:卡住待处理(等依赖/等主窗口裁决)', empty: '暂无阻塞——任务卡住待裁决时会出现在这里' }
|
|
243
248
|
}
|
|
244
249
|
var reqEpoch = 0 // 会话切换纪元:切会话时自增,旧会话在途响应按纪元丢弃
|
|
245
|
-
var state = { sessionId: null, tasks: [], boardMode: 'auto', teamMode: false, workMode: 'auto', minWorkers: 1, maxWorkers: 3, minVerifiers: 0, maxVerifiers: 2, workerModel: '', verifierModel: '', softTimeoutMin: 30, hardTimeoutMin: 120, feedbackEnabled: true, notifyDispatch: true, notifyDone: true, epicSplit: true, verifyUserGuide: true, lessonPushed: {}, isRoot: true, isRootEverTrue: false, isRootFalseN: 0, isRootStable: true, open: false, detailId: null, children: [], dragOver: null, dragTask: null, dispatchInfo: '', view: 'board', layoutLeft: 280, layoutRight: 0, poolStatus: null, escalatedIds: [], filterQ: '', filterPrio: [], filterTag: '', selectMode: false, selected: {}, undoSnapshot: null, cardHover: '', archSort: 'time-desc', dateRange: { from: '', to: '' }, rfOpen: false, gboOpen: false, activity: {}, archived: [], archQ: '', globalBoards: [], createOpen: false, createFlash: '', mpRole: '', mpSize: '', usageSummary: null, childStats: {}, tasksErr: '', tasksHash: '' }
|
|
250
|
+
var state = { sessionId: null, tasks: [], boardMode: 'auto', teamMode: false, workMode: 'auto', minWorkers: 1, maxWorkers: 3, minVerifiers: 0, maxVerifiers: 2, workerModel: '', verifierModel: '', softTimeoutMin: 30, hardTimeoutMin: 120, feedbackEnabled: true, notifyDispatch: true, notifyDone: true, epicSplit: true, verifyUserGuide: true, lessonPushed: {}, isRoot: true, isRootEverTrue: false, isRootFalseN: 0, isRootStable: true, open: false, detailId: null, children: [], dragOver: null, dragTask: null, dispatchInfo: '', view: 'board', layoutLeft: 280, layoutRight: 0, poolStatus: null, escalatedIds: [], filterQ: '', filterPrio: [], filterTag: '', filterReview: false, selectMode: false, selected: {}, undoSnapshot: null, cardHover: '', archSort: 'time-desc', dateRange: { from: '', to: '' }, rfOpen: false, gboOpen: false, activity: {}, archived: [], archQ: '', globalBoards: [], createOpen: false, createFlash: '', mpRole: '', mpSize: '', usageSummary: null, childStats: {}, tasksErr: '', tasksHash: '' }
|
|
246
251
|
|
|
247
252
|
// #16 快捷键:Esc 逐级关闭(详情→看板→面板);输入框聚焦时不劫持
|
|
248
253
|
try {
|
|
@@ -364,10 +369,14 @@ function apply(ctx) {
|
|
|
364
369
|
state.poolStatus = (d && d.poolStatus) || null
|
|
365
370
|
// token 消耗聚合(board 级,host 端现算):范围守卫——响应回来时范围若已变,保留旧值不覆盖
|
|
366
371
|
// (新范围的那次请求会带着新聚合回来;老 host 不返回该字段 → null,零渲染)
|
|
367
|
-
// ===== usageSummary
|
|
368
|
-
//
|
|
369
|
-
// →
|
|
370
|
-
//
|
|
372
|
+
// ===== usageSummary 变化检测 + React 订阅(task-muwq9u04 → task-mv10lvud 根修)=====
|
|
373
|
+
// 病根(task-mv10lvud 实测「点今天也没变化」):范围切换时 setRange 先换 state.dateRange(新对象
|
|
374
|
+
// → setDR 身份变化 → 第一帧渲染成功),随后 fetchTasks 响应把 state.usageSummary 换成新对象并 notify(),
|
|
375
|
+
// 但 TopPanel 的 update() 里所有 setX 值都 Object.is 全等(tasks 引用不变、布尔不变)→ React 18 全量
|
|
376
|
+
// bailout,零重渲染 → Token 区永远拿不到新聚合。notify ≠ React 重渲染,须有「新身份的订阅值」做载体。
|
|
377
|
+
// 修法:TopPanel 补 usageSummary 专属 React state(setUsage(state.usageSummary)),host 每次响应都是
|
|
378
|
+
// 新对象,身份变化天然触发渲染(订阅见 TopPanel 的 update())。
|
|
379
|
+
// 为什么本处用「上一次的 JSON 串」比对而不是对象引用:host 每次 get-tasks 都现算聚合、**必然返回新对象**
|
|
371
380
|
// (引用比较恒真)→ 3s 轮询每轮都 notify,tasksHash 的渲染节约当场作废。JSON 串只在这份聚合
|
|
372
381
|
// 真变了时才不等(KB 级体积、3s 一次,代价可忽略);赋值前先快照,赋值后比对。
|
|
373
382
|
var usageJsonPrev = JSON.stringify(state.usageSummary || null)
|
|
@@ -511,18 +520,40 @@ function apply(ctx) {
|
|
|
511
520
|
}).catch(function (e) { if (onMsg) onMsg('⚠️ ' + String(e)); return { ok: false, error: String(e) } })
|
|
512
521
|
}
|
|
513
522
|
|
|
523
|
+
// ===== 取消通道(与 host cancel-task / task_cancel 门禁一一对应)=====
|
|
524
|
+
// 可取消状态:草稿/待办/阻塞(未进入执行)。in-progress/verifying 需先终止;resolved 引导归档。
|
|
525
|
+
// 取消=显式放弃:置 cancelled + 留档可检索、可在归档 tab「恢复待办」;与删除不同,取消不丢数据。
|
|
526
|
+
function canCancel(t) { return !!t && (t.status === 'draft' || t.status === 'pending' || t.status === 'blocked') }
|
|
527
|
+
// confirm 预警(与 note「取消后不再派发,可在归档恢复」同口径);失败时 host 门禁原因原样回显。
|
|
528
|
+
function cancelTask(id, title, onMsg) {
|
|
529
|
+
var t = getTask(id)
|
|
530
|
+
var name = title || (t && t.title) || id
|
|
531
|
+
if (!window.confirm('取消后不再派发,可在归档恢复。确认取消「' + name + '」?')) return Promise.resolve({ ok: false, cancelled: true })
|
|
532
|
+
return rpc('cancel-task', { taskId: id }).then(function (r) {
|
|
533
|
+
if (r && r.ok === false) { if (onMsg) onMsg('⚠️ ' + (r.error || '取消失败')) }
|
|
534
|
+
else if (onMsg) onMsg('🚫 已取消「' + name + '」(可在归档 tab 恢复待办)')
|
|
535
|
+
fetchTasks()
|
|
536
|
+
return r
|
|
537
|
+
}).catch(function (e) { if (onMsg) onMsg('⚠️ ' + String(e)); return { ok: false, error: String(e) } })
|
|
538
|
+
}
|
|
539
|
+
|
|
514
540
|
function onDragStart(e, task) { e.dataTransfer.setData('text/plain', JSON.stringify({ id: task.id, status: task.status })); e.dataTransfer.effectAllowed = 'move'; state.dragTask = task.id; notify() }
|
|
515
541
|
function onDragEnd() { state.dragTask = null; state.dragOver = null; notify() }
|
|
516
542
|
function onColDragOver(e, st) { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; if (state.dragOver !== st) { state.dragOver = st; notify() } }
|
|
517
543
|
function onColDragLeave(st) { if (state.dragOver === st) { state.dragOver = null; notify() } }
|
|
518
544
|
function onColDrop(e, st) { e.preventDefault(); try { var data = JSON.parse(e.dataTransfer.getData('text/plain')); state.dragOver = null; state.dragTask = null; transition(data.id, data.status, st); notify() } catch (_) {} }
|
|
519
545
|
|
|
520
|
-
//
|
|
546
|
+
// ===== 风险队列「待你过目」客户端单一事实源(异常驱动审视①)=====
|
|
547
|
+
// 命中=host 现算的 reviewHint.score>0;待过目=命中 + 未阅(reviewedAt 空)+ 终态(resolved/archived)。
|
|
548
|
+
// 徽章 / chip 过滤 / digest 计数三处同用这一个谓词,避免口径分叉。
|
|
549
|
+
function reviewPending(t) { return !!(t && t.reviewHint && t.reviewHint.score > 0 && !t.reviewedAt && (t.status === 'resolved' || t.status === 'archived')) }
|
|
550
|
+
// #13 筛选:文本(标题/描述/ID)+ 优先级多选 + 标签 + 待过目队列
|
|
521
551
|
function passFilter(t) {
|
|
522
552
|
var q = state.filterQ.trim().toLowerCase()
|
|
523
553
|
if (q && (t.title + ' ' + (t.description || '') + ' ' + t.id).toLowerCase().indexOf(q) < 0) return false
|
|
524
554
|
if (state.filterPrio.length > 0 && state.filterPrio.indexOf(t.priority || 'medium') < 0) return false
|
|
525
555
|
if (state.filterTag && (t.tags || []).indexOf(state.filterTag) < 0) return false
|
|
556
|
+
if (state.filterReview && !reviewPending(t)) return false
|
|
526
557
|
return true
|
|
527
558
|
}
|
|
528
559
|
function allTags() { var s = {}; state.tasks.forEach(function (t) { (t.tags || []).forEach(function (g) { s[g] = 1 }) }); return Object.keys(s) }
|
|
@@ -584,15 +615,16 @@ function apply(ctx) {
|
|
|
584
615
|
var _ndo = useState(state.notifyDispatch), ndOn = _ndo[0], setNdOn = _ndo[1]; var _nno = useState(state.notifyDone), nnOn = _nno[0], setNnOn = _nno[1]
|
|
585
616
|
var _eso = useState(state.epicSplit), esOn = _eso[0], setEsOn = _eso[1] // 史诗拆分总开关勾选态(设置区「功能」)
|
|
586
617
|
var _vug = useState(state.verifyUserGuide), vugOn = _vug[0], setVugOn = _vug[1] // 自测指南开关勾选态(设置区「验收」)
|
|
587
|
-
|
|
618
|
+
var _usg = useState(state.usageSummary), setUsage = _usg[1] // Token 区聚合订阅(task-mv10lvud 根修):usageSummary 换新对象即触发渲染
|
|
619
|
+
useEffect(function () { function update() { setOpen(state.open); setTasksState(state.tasks); setModeState(state.boardMode); setDetailId(state.detailId); setDragOver(state.dragOver); setDispatchInfo(state.dispatchInfo); setViewState(state.view); setLayL(state.layoutLeft); setLayR(state.layoutRight); setMinW(state.minWorkers); setMaxW(state.maxWorkers); setMinV(state.minVerifiers); setMaxV(state.maxVerifiers); setWorkerModel(state.workerModel); setVerifierModel(state.verifierModel); setTeamMode(state.teamMode); setWorkModeState(state.workMode); setDR(state.dateRange); setSoftT(state.softTimeoutMin); setHardT(state.hardTimeoutMin); setFbEnabled(state.feedbackEnabled); setNdOn(state.notifyDispatch); setNnOn(state.notifyDone); setEsOn(state.epicSplit); setVugOn(state.verifyUserGuide); setCreateOpen(state.createOpen); setCreateFlash(state.createFlash); setTasksErr(state.tasksErr); setUsage(state.usageSummary) }; listeners.push(update); update(); return function () { var i = listeners.indexOf(update); if (i >= 0) listeners.splice(i, 1) } }, [])
|
|
588
620
|
if (!open) return null
|
|
589
621
|
if (!state.isRootStable) return null // 子代理会话不渲染看板面板(读 isRootStable:瞬态 false 不闪,见 isRoot 蝶变防抖)
|
|
590
622
|
var active = tasks.filter(function (t) { return t.status !== 'archived' }); var archived = tasks.filter(function (t) { return t.status === 'archived' })
|
|
591
|
-
var hasFilter = state.filterQ.trim() || state.filterPrio.length > 0 || state.filterTag
|
|
623
|
+
var hasFilter = state.filterQ.trim() || state.filterPrio.length > 0 || state.filterTag || state.filterReview
|
|
592
624
|
if (hasFilter) { active = active.filter(passFilter); archived = archived.filter(passFilter) }
|
|
593
625
|
var content
|
|
594
626
|
if (view === 'dashboard') { content = React.createElement(Dashboard) }
|
|
595
|
-
else if (view === 'archive') { content = React.createElement(ArchiveView) }
|
|
627
|
+
else if (view === 'archive') { content = detailId ? React.createElement(DetailView) : React.createElement(ArchiveView) }
|
|
596
628
|
else if (view === 'team') { content = React.createElement(TeamView) }
|
|
597
629
|
else if (detailId) { content = React.createElement(DetailView) }
|
|
598
630
|
else {
|
|
@@ -687,12 +719,14 @@ function apply(ctx) {
|
|
|
687
719
|
useEffect(function () { function update() { setQ(state.filterQ); setFt(state.filterTag) }; listeners.push(update); update(); return function () { var i = listeners.indexOf(update); if (i >= 0) listeners.splice(i, 1) } }, [])
|
|
688
720
|
function setQ2(v) { state.filterQ = v; notify() }
|
|
689
721
|
function togglePrio(p) { var i = state.filterPrio.indexOf(p); if (i >= 0) state.filterPrio.splice(i, 1); else state.filterPrio.push(p); notify() }
|
|
690
|
-
function
|
|
691
|
-
|
|
722
|
+
function toggleReview() { state.filterReview = !state.filterReview; notify() }
|
|
723
|
+
function clearAll() { state.filterQ = ''; state.filterPrio = []; state.filterTag = ''; state.filterReview = false; notify() }
|
|
724
|
+
var hasFilter = q.trim() || state.filterPrio.length > 0 || ft || state.filterReview
|
|
692
725
|
var chipBase = { fontSize: 10, padding: '2px 8px', borderRadius: 10, border: '1px solid ' + C.border, cursor: 'pointer', background: 'transparent', color: C.text2, transition: 'all .15s' }
|
|
693
726
|
return React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 6, marginBottom: 8, flexWrap: 'wrap' } },
|
|
694
727
|
React.createElement('input', { value: q, onChange: function (e) { setQ2(e.target.value) }, placeholder: '🔍 搜索标题/描述/ID…', style: { flex: '0 1 200px', fontSize: 11, padding: '3px 8px', border: '1px solid ' + C.border2, borderRadius: 4, background: C.card, color: C.text } }),
|
|
695
728
|
['critical', 'high', 'medium', 'low'].map(function (p) { var on = state.filterPrio.indexOf(p) >= 0; return React.createElement('button', { key: p, onClick: function () { togglePrio(p) }, style: Object.assign({}, chipBase, on ? { background: prioColor[p], color: C_INV, borderColor: prioColor[p] } : {}) }, prioLabel[p]) }),
|
|
729
|
+
React.createElement('button', { key: 'review', onClick: toggleReview, title: '只看命中风险信号且未阅的已完成/归档卡(已阅卡不计)', style: Object.assign({}, chipBase, state.filterReview ? { background: C.warn, color: C_INV, borderColor: C.warn } : {}) }, '👁 待你过目'),
|
|
696
730
|
allTags().length > 0 ? React.createElement('select', { value: ft, onChange: function (e) { state.filterTag = e.target.value; notify() }, style: { fontSize: 10, padding: '2px 4px', border: '1px solid ' + C.border, borderRadius: 4, background: C.card, color: C.text } }, React.createElement('option', { value: '' }, '🏷 全部标签'), allTags().map(function (g) { return React.createElement('option', { key: g, value: g }, g) })) : null,
|
|
697
731
|
hasFilter ? React.createElement('button', { onClick: clearAll, style: { fontSize: 10, padding: '2px 8px', borderRadius: 10, border: 'none', background: C.nested, color: C.text2, cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 2 } }, ic('x', 9), '清除') : null)
|
|
698
732
|
}
|
|
@@ -789,7 +823,7 @@ function apply(ctx) {
|
|
|
789
823
|
// Enter/Space 触发与点击相同的激活行为(多选=切换选中,否则开详情);
|
|
790
824
|
// focus 态用主题色 C.brand 2px outline(不用浏览器默认蓝框,保持主题一致)
|
|
791
825
|
function activate() { if (state.selectMode) { if (state.selected[t.id]) delete state.selected[t.id]; else state.selected[t.id] = true; notify() } else { state.detailId = t.id; notify() } }
|
|
792
|
-
return React.createElement('div', { role: 'button', tabIndex: 0, 'aria-label': t.title + '(' + (statusLabels[t.status] || t.status) + ')', draggable: !state.selectMode, onDragStart: function (e) { onDragStart(e, t) }, onDragEnd: onDragEnd, onMouseEnter: function () { if (delOk) setHover(t.id, 'del') }, onMouseLeave: function () { if (delOk) setHover('', '') }, onFocus: function () { setCardFocus(t.id) }, onBlur: function () { setCardFocus('') }, onKeyDown: function (e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); activate() } }, onClick: activate, style: { outline: isCardFocus(t.id) ? '2px solid ' + C.brand : 'none', outlineOffset: 2, border: '1px solid ' + (sel ? C.brand : (t.escalation ? C.err : (critGlow ? C.err : C.border))), borderRadius: 6, padding: '6px 8px', marginBottom: 6, background: sel ? C.nested : C.card, borderLeft: '3px solid ' + (t.escalation ? C.err : pc), cursor: state.selectMode ? 'pointer' : 'grab', fontSize: 12, opacity: dragging ? 0.4 : (depBlock ? 0.65 : 1), transition: 'opacity .15s', animation: critGlow ? 'tskb-crit 2s ease-in-out infinite' : 'none' } }, React.createElement('div', { style: { display: 'flex', alignItems: 'flex-start', gap: 4 } }, state.selectMode ? React.createElement('span', { style: { color: sel ? C.brand : C.text2, flexShrink: 0, marginTop: 1, display: 'inline-flex' } }, ic(sel ? 'square-check-big' : 'square', 12)) : null, React.createElement('div', { style: { fontWeight: 600, color: C.text, marginBottom: 2, wordBreak: 'break-word', flex: 1 } }, t.title), React.createElement('span', { style: { flexShrink: 0, marginTop: 1, display: 'inline-flex', color: C.text2 }, title: pm.label }, ic(pm.icon, 10)), React.createElement('span', { style: { fontSize: 9, padding: '1px 5px', borderRadius: 3, background: 'color-mix(in srgb, ' + pc + ' 20%, transparent)', color: pc, flexShrink: 0, marginTop: 1 } }, prioLabel[t.priority] || '中'), noResearch(t) ? React.createElement('span', { title: '本任务未附调研上下文,Worker 需自行定位——建议补 contextFiles/contextNotes', style: { fontSize: 9, padding: '1px 5px', borderRadius: 3, background: 'color-mix(in srgb, ' + C.warn + ' 18%, transparent)', color: C.warn, fontWeight: 600, flexShrink: 0, marginTop: 1, whiteSpace: 'nowrap' } }, '⚠️ 无调研') : null, (t.usage && t.usage.total) ?React.createElement('span', { style: { fontSize: 9, color: C.text2, flexShrink: 0, marginTop: 1 }, title: '本任务累计 token:' + String(t.usage.total) + '(' + (t.usage.runs || 0) + ' 次 run)' }, '⛁ ' + fmtTokens(t.usage.total)) : null), t.parentId ? React.createElement('div', { style: { fontSize: 10, color: C.text2, marginBottom: 2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, title: parentT ? '父任务:' + parentT.title + ' (' + t.parentId + ')' : t.parentId }, '↳ ' + (parentT && parentT.title ? parentT.title : shortId(t.parentId))) : null, t.escalation ? React.createElement('div', { style: { fontSize: 10, color: C.err, fontWeight: 600, marginBottom: 2, display: 'flex', alignItems: 'center', gap: 3 } }, ic('alert-triangle', 10), '待裁决 — 点击查看疑问') : null, t.stuckSince ? React.createElement('div', { style: { fontSize: 10, color: C.warn, fontWeight: 600, marginBottom: 2, animation: 'tskb-pulse 1.5s ease-in-out infinite' } }, '⏱ 疑似卡死 · ' + ago(t.stuckSince) + ' — 点击处理') : null, React.createElement('div', { style: { fontSize: 10, color: C.text2, display: 'flex', alignItems: 'center', gap: 5, flexWrap: 'wrap' } },
|
|
826
|
+
return React.createElement('div', { role: 'button', tabIndex: 0, 'aria-label': t.title + '(' + (statusLabels[t.status] || t.status) + ')', draggable: !state.selectMode, onDragStart: function (e) { onDragStart(e, t) }, onDragEnd: onDragEnd, onMouseEnter: function () { if (delOk) setHover(t.id, 'del') }, onMouseLeave: function () { if (delOk) setHover('', '') }, onFocus: function () { setCardFocus(t.id) }, onBlur: function () { setCardFocus('') }, onKeyDown: function (e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); activate() } }, onClick: activate, style: { outline: isCardFocus(t.id) ? '2px solid ' + C.brand : 'none', outlineOffset: 2, border: '1px solid ' + (sel ? C.brand : (t.escalation ? C.err : (critGlow ? C.err : C.border))), borderRadius: 6, padding: '6px 8px', marginBottom: 6, background: sel ? C.nested : C.card, borderLeft: '3px solid ' + (t.escalation ? C.err : pc), cursor: state.selectMode ? 'pointer' : 'grab', fontSize: 12, opacity: dragging ? 0.4 : (depBlock ? 0.65 : 1), transition: 'opacity .15s', animation: critGlow ? 'tskb-crit 2s ease-in-out infinite' : 'none' } }, React.createElement('div', { style: { display: 'flex', alignItems: 'flex-start', gap: 4 } }, state.selectMode ? React.createElement('span', { style: { color: sel ? C.brand : C.text2, flexShrink: 0, marginTop: 1, display: 'inline-flex' } }, ic(sel ? 'square-check-big' : 'square', 12)) : null, React.createElement('div', { style: { fontWeight: 600, color: C.text, marginBottom: 2, wordBreak: 'break-word', flex: 1 } }, t.title), React.createElement('span', { style: { flexShrink: 0, marginTop: 1, display: 'inline-flex', color: C.text2 }, title: pm.label }, ic(pm.icon, 10)), React.createElement('span', { style: { fontSize: 9, padding: '1px 5px', borderRadius: 3, background: 'color-mix(in srgb, ' + pc + ' 20%, transparent)', color: pc, flexShrink: 0, marginTop: 1 } }, prioLabel[t.priority] || '中'), reviewPending(t) ? React.createElement('span', { title: '👁 建议过目(命中风险信号):\n' + ((t.reviewHint && t.reviewHint.reasons) || []).join('\n'), style: { fontSize: 9, padding: '0 4px', borderRadius: 2, background: 'color-mix(in srgb, ' + C.warn + ' 22%, transparent)', color: C.warn, fontWeight: 600, whiteSpace: 'nowrap', flexShrink: 0, marginTop: 1 } }, '👁 建议过目') : null, noResearch(t) ? React.createElement('span', { title: '本任务未附调研上下文,Worker 需自行定位——建议补 contextFiles/contextNotes', style: { fontSize: 9, padding: '1px 5px', borderRadius: 3, background: 'color-mix(in srgb, ' + C.warn + ' 18%, transparent)', color: C.warn, fontWeight: 600, flexShrink: 0, marginTop: 1, whiteSpace: 'nowrap' } }, '⚠️ 无调研') : null, (t.usage && t.usage.total) ?React.createElement('span', { style: { fontSize: 9, color: C.text2, flexShrink: 0, marginTop: 1 }, title: '本任务累计 token:' + String(t.usage.total) + '(' + (t.usage.runs || 0) + ' 次 run)' }, '⛁ ' + fmtTokens(t.usage.total)) : null), t.parentId ? React.createElement('div', { style: { fontSize: 10, color: C.text2, marginBottom: 2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, title: parentT ? '父任务:' + parentT.title + ' (' + t.parentId + ')' : t.parentId }, '↳ ' + (parentT && parentT.title ? parentT.title : shortId(t.parentId))) : null, t.escalation ? React.createElement('div', { style: { fontSize: 10, color: C.err, fontWeight: 600, marginBottom: 2, display: 'flex', alignItems: 'center', gap: 3 } }, ic('alert-triangle', 10), '待裁决 — 点击查看疑问') : null, t.stuckSince ? React.createElement('div', { style: { fontSize: 10, color: C.warn, fontWeight: 600, marginBottom: 2, animation: 'tskb-pulse 1.5s ease-in-out infinite' } }, '⏱ 疑似卡死 · ' + ago(t.stuckSince) + ' — 点击处理') : null, React.createElement('div', { style: { fontSize: 10, color: C.text2, display: 'flex', alignItems: 'center', gap: 5, flexWrap: 'wrap' } },
|
|
793
827
|
epic ? React.createElement('span', { title: '史诗父卡:' + csDone + '/' + cs.total + ' 个子任务已了结(完成/取消/归档)', style: { fontSize: 9, padding: '0 4px', borderRadius: 2, background: 'color-mix(in srgb, ' + C.brand + ' 14%, transparent)', color: C.brand, fontWeight: 600 } }, '📦 史诗 · ' + csDone + '/' + cs.total) : null,
|
|
794
828
|
// hook 相位徽章:与 📦 徽章**并存**(相位是 epic 的附加过程态,不是替代进度)
|
|
795
829
|
epic ? hookPhaseBadge(t) : null,
|
|
@@ -819,35 +853,49 @@ function apply(ctx) {
|
|
|
819
853
|
function ArchiveView() {
|
|
820
854
|
var _R = React; var useState = _R.useState
|
|
821
855
|
var _a = useState(state.archQ || ''), q = _a[0], setQ = _a[1]
|
|
822
|
-
//
|
|
856
|
+
// 归档 tab 数据源口径对齐仪表盘(反馈 n-muyg3e0m9z7i):直接读 state.tasks(get-tasks 全量含归档),
|
|
823
857
|
// 不再依赖独立 state.archived/fetchArchived——旧路径异步填库却未接入 TopPanel 的 notify 更新项,
|
|
824
858
|
// 归档 tab 打开后停在「暂无归档任务」空态,而仪表盘「已归档」计数来自 state.tasks(全量),二者对不上。
|
|
825
|
-
// 全量 +
|
|
826
|
-
|
|
859
|
+
// 全量 + 按归档/取消时间倒序(archivedAt 优先,回退 cancelledAt/resolvedAt/createdAt)+ 顶部注明条数。
|
|
860
|
+
// 含 cancelled(反馈 n-mv0zbqk... 取消通道):取消卡在待办列消失(cancelled 不在 COLUMNS)后,
|
|
861
|
+
// 归入本 tab 可见、可「恢复待办」——否则作废卡从 UI 上彻底蒸发、无法找回。
|
|
862
|
+
var archived = state.tasks.filter(function (t) { return t.status === 'archived' || t.status === 'cancelled' })
|
|
863
|
+
var cancelledCount = archived.filter(function (t) { return t.status === 'cancelled' }).length // 文案口径修正:仪表盘「已归档」只数 archived,本 tab 含 cancelled,须单列
|
|
827
864
|
var list = archived.filter(function (t) {
|
|
828
865
|
if (!q) return true
|
|
829
866
|
var qq = q.toLowerCase()
|
|
830
867
|
return (t.title || '').toLowerCase().indexOf(qq) >= 0 || (t.id || '').toLowerCase().indexOf(qq) >= 0 || (t.tags || []).join(' ').toLowerCase().indexOf(qq) >= 0
|
|
831
868
|
}).sort(function (a, b) {
|
|
832
|
-
//
|
|
833
|
-
function archTs(t) { return t.archivedAt || t.resolvedAt || t.createdAt || '' }
|
|
869
|
+
// 归档/取消时间倒序:archivedAt 优先,回退 cancelledAt/resolvedAt/createdAt(老卡可能缺 archivedAt)
|
|
870
|
+
function archTs(t) { return t.archivedAt || t.cancelledAt || t.resolvedAt || t.createdAt || '' }
|
|
834
871
|
return (archTs(b) || '').localeCompare(archTs(a) || '')
|
|
835
872
|
})
|
|
836
|
-
|
|
873
|
+
// 工作模式(与 fetchTasks 同口径):list=清单模式(恢复不重派)不弹;auto/team 恢复即重派 → confirm 门禁
|
|
874
|
+
var wm = state.workMode || (state.teamMode ? 'team' : (state.boardMode === 'auto' ? 'auto' : 'list'))
|
|
875
|
+
function restore(id) {
|
|
876
|
+
if (wm !== 'list') {
|
|
877
|
+
if (!window.confirm('恢复待办后将按当前工作模式立即重新派发,消耗一轮 Worker+Verifier token。继续?')) return
|
|
878
|
+
}
|
|
879
|
+
rpc('update-task', { taskId: id, resetToPending: true }).then(function () { fetchTasks() }).catch(function () {})
|
|
880
|
+
}
|
|
837
881
|
return React.createElement('div', null,
|
|
838
882
|
React.createElement('input', { value: q, onChange: function (e) { setQ(e.target.value); state.archQ = e.target.value }, placeholder: '检索标题 / ID / 标签…', style: { width: '100%', padding: '5px 8px', fontSize: 11, border: '1px solid ' + C.border2, borderRadius: 5, background: C.card, color: C.text, marginBottom: 8 } }),
|
|
839
|
-
React.createElement('div', { style: { fontSize: 10, color: C.text2, marginBottom: 6 } }, '共 ' + archived.length + '
|
|
840
|
-
archived.length === 0 ? React.createElement('div', { style: { fontSize: 11, color: C.text2, padding: 12, textAlign: 'center' } }, '
|
|
883
|
+
React.createElement('div', { style: { fontSize: 10, color: C.text2, marginBottom: 6 }, title: '归档 tab 含已归档 + 已取消两类;仪表盘「已归档」只数已归档,不数已取消' }, '共 ' + archived.length + ' 条' + (cancelledCount > 0 ? '(含已取消 ' + cancelledCount + ' 条)' : '')),
|
|
884
|
+
archived.length === 0 ? React.createElement('div', { style: { fontSize: 11, color: C.text2, padding: 12, textAlign: 'center' } }, '暂无归档/已取消任务') :
|
|
841
885
|
list.length === 0 ? React.createElement('div', { style: { fontSize: 11, color: C.text2, padding: 12, textAlign: 'center' } }, '无匹配结果') :
|
|
842
886
|
list.map(function (t) {
|
|
843
|
-
|
|
887
|
+
// 归档行点击进详情(用户 2026-10-09 指令):复用 state.detailId 机制,与看板卡同路径;
|
|
888
|
+
// 详情页对 archived 的操作区既有门禁不回归(恢复待办=保存并重置、删除仅 draft/pending/blocked)。
|
|
889
|
+
return React.createElement('div', { key: t.id, onClick: function () { state.detailId = t.id; notify() }, title: '点击查看详情', style: { padding: '6px 8px', marginBottom: 5, background: C.card, border: '1px solid ' + C.border, borderRadius: 6, cursor: 'pointer' } },
|
|
844
890
|
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 6 } },
|
|
845
891
|
React.createElement('span', { style: { fontSize: 11, fontWeight: 600, color: C.text, flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, title: t.title }, t.title),
|
|
892
|
+
reviewPending(t) ? React.createElement('span', { title: '👁 建议过目(命中风险信号):\n' + ((t.reviewHint && t.reviewHint.reasons) || []).join('\n'), style: { fontSize: 9, padding: '0 4px', borderRadius: 2, background: 'color-mix(in srgb, ' + C.warn + ' 22%, transparent)', color: C.warn, fontWeight: 600, whiteSpace: 'nowrap', flexShrink: 0 } }, '👁 建议过目') : null,
|
|
846
893
|
t.verification ? React.createElement('span', { title: '验收结论: ' + (t.verification.summary || '').slice(0, 100), style: { fontSize: 9, padding: '1px 5px', borderRadius: 3, background: t.verification.verdict === 'approved' ? C.ok : C.err, color: C_INV } }, t.verification.verdict === 'approved' ? '过' : '驳') : null,
|
|
847
|
-
React.createElement('span', { style: { fontSize: 9,
|
|
894
|
+
t.status === 'cancelled' ? React.createElement('span', { title: '已取消(可恢复待办)', style: { fontSize: 9, padding: '0 4px', borderRadius: 2, background: 'color-mix(in srgb, ' + C.warn + ' 20%, transparent)', color: C.warn, fontWeight: 600, whiteSpace: 'nowrap', flexShrink: 0 } }, '已取消') : null,
|
|
895
|
+
React.createElement('span', { style: { fontSize: 9, color: C.text2 } }, (t.archivedAt || t.cancelledAt) ? ago(t.archivedAt || t.cancelledAt) : '')),
|
|
848
896
|
t.verification && t.verification.summary ? React.createElement('div', { style: { fontSize: 10, color: C.text2, marginTop: 2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, '📝 ' + t.verification.summary) : null,
|
|
849
897
|
React.createElement('div', { style: { display: 'flex', gap: 5, marginTop: 4, flexWrap: 'wrap' } },
|
|
850
|
-
React.createElement('button', { onClick: function () { restore(t.id) }, title: '恢复为待办', style: { fontSize: 9, padding: '2px 7px', border: '1px solid ' + C.brand, borderRadius: 3, background: 'transparent', color: C.brand, cursor: 'pointer' } }, '↩ 恢复待办'),
|
|
898
|
+
React.createElement('button', { onClick: function (e) { e.stopPropagation(); restore(t.id) }, title: wm === 'list' ? '恢复为待办' : '恢复为待办(将按当前工作模式立即重新派发,消耗一轮 Worker+Verifier token)', style: { fontSize: 9, padding: '2px 7px', border: '1px solid ' + C.brand, borderRadius: 3, background: 'transparent', color: C.brand, cursor: 'pointer' } }, '↩ 恢复待办'),
|
|
851
899
|
(function () {
|
|
852
900
|
var runs = historyRuns(t)
|
|
853
901
|
if (!runs.length) return React.createElement('span', { style: { fontSize: 9, color: C.text2, opacity: 0.6, padding: '2px 0' } }, '无历史会话')
|
|
@@ -856,7 +904,7 @@ function apply(ctx) {
|
|
|
856
904
|
var seq = r.role === 'verifier' ? (++vN) : (++wN)
|
|
857
905
|
var isV = r.role === 'verifier'
|
|
858
906
|
return React.createElement('button', {
|
|
859
|
-
key: i, onClick: function () { if (uiWorkspaceSvc) uiWorkspaceSvc.openSession(r.id) },
|
|
907
|
+
key: i, onClick: function (e) { e.stopPropagation(); if (uiWorkspaceSvc) uiWorkspaceSvc.openSession(r.id) },
|
|
860
908
|
title: (isV ? 'Verifier' : 'Worker') + ' 第 ' + seq + ' 次' + (r.at ? ' · ' + ago(r.at) : '') + (r.model ? ' · ' + r.model : '') + '(' + r.id + ')',
|
|
861
909
|
style: { fontSize: 9, padding: '2px 7px', border: '1px solid ' + (isV ? C.warn : C.brand), borderRadius: 3, background: 'transparent', color: isV ? C.warn : C.brand, cursor: 'pointer' }
|
|
862
910
|
}, '→ ' + (isV ? 'V' : 'W') + '#' + seq)
|
|
@@ -877,6 +925,7 @@ function apply(ctx) {
|
|
|
877
925
|
var _tc = useState(''), touchesRaw = _tc[0], setTouchesRaw = _tc[1]
|
|
878
926
|
var _dp = useState({}), depSel = _dp[0], setDepSel = _dp[1]
|
|
879
927
|
var _ac = useState(''), acc = _ac[0], setAcc = _ac[1]
|
|
928
|
+
var _tg = useState(''), tagsRaw = _tg[0], setTagsRaw = _tg[1]
|
|
880
929
|
var _dr = useState(wm === 'team'), asDraft = _dr[0], setAsDraft = _dr[1]
|
|
881
930
|
var _bs = useState(false), busy = _bs[0], setBusy = _bs[1]
|
|
882
931
|
var _er = useState(''), err = _er[0], setErr = _er[1]
|
|
@@ -891,9 +940,10 @@ function apply(ctx) {
|
|
|
891
940
|
var t = title.trim()
|
|
892
941
|
if (!t) { setErr('⚠️ 请先填写标题'); return }
|
|
893
942
|
var touches = touchesRaw.split(/[\n,,;;]/).map(function (s) { return s.trim() }).filter(function (s) { return s.length > 0 })
|
|
943
|
+
var tags = tagsRaw.split(/[,,]/).map(function (s) { return s.trim() }).filter(function (s) { return s.length > 0 })
|
|
894
944
|
var deps = cands.filter(function (x) { return depSel[x.id] }).map(function (x) { return x.id })
|
|
895
945
|
setBusy(true); setErr(''); setOkMsg(''); setWarn('')
|
|
896
|
-
rpc('create-task', { title: t, description: desc, priority: prio, pipeline: pipe, touches: touches, dependsOn: deps, acceptance: acc.trim(), draft: asDraft }).then(function (r) {
|
|
946
|
+
rpc('create-task', { title: t, description: desc, priority: prio, pipeline: pipe, touches: touches, tags: tags, dependsOn: deps, acceptance: acc.trim(), draft: asDraft }).then(function (r) {
|
|
897
947
|
setBusy(false)
|
|
898
948
|
if (r && r.ok === false) { setErr('⚠️ ' + (r.error || '创建失败')); return }
|
|
899
949
|
fetchTasks()
|
|
@@ -929,6 +979,7 @@ function apply(ctx) {
|
|
|
929
979
|
React.createElement('option', { value: 'full' }, '全流程(执行+验证)'),
|
|
930
980
|
React.createElement('option', { value: 'work' }, '免验证(只做不验)'),
|
|
931
981
|
React.createElement('option', { value: 'direct' }, '主窗口处理')), '管线 = 任务走哪种流程:全流程 = Worker 做 + Verifier 验;免验证 = 只做不验;主窗口处理 = 你在对话里直接办'))),
|
|
982
|
+
field('标签(逗号分隔,可空)', React.createElement('input', { value: tagsRaw, onChange: function (e) { setTagsRaw(e.target.value) }, placeholder: '如 bug, epic, 待验收', style: inp }), '标签 = 用于筛选的逗号分隔关键字(自动 trim + 去空)'),
|
|
932
983
|
field('touches(文件/glob,逗号或换行分隔,可空)', React.createElement('input', { value: touchesRaw, onChange: function (e) { setTouchesRaw(e.target.value) }, placeholder: 'src/a.mjs, src/**', style: inp }), 'touches = 本卡要改动的文件(glob 匹配);声明后并行任务间防互踩(文件锁)'),
|
|
933
984
|
field('依赖(全部完成后才派发,可空)', cands.length === 0
|
|
934
985
|
? React.createElement('div', { style: { fontSize: 10, color: C.text2 } }, '当前无草稿/待办任务可选')
|
|
@@ -1172,11 +1223,79 @@ function apply(ctx) {
|
|
|
1172
1223
|
React.createElement('div', { style: { fontSize: 9, color: C.text2, marginTop: 4, lineHeight: 1.6 } }, '每个点位是一次真实 agent 运行(不是声明式命令):prompt 只给薄框架契约与上下文,动作由 hook agent 自行决策,吃不准会歧义上报;失败即转阻塞等人裁决(重试/跳过/放弃),不会自动重跑。清空 prompt 保存 = 撤掉该点位。hooks 仅主窗口可设(host 门禁,失败原因原样回显)。'))
|
|
1173
1224
|
}
|
|
1174
1225
|
|
|
1226
|
+
// ===== 审视摘要区(异常驱动审视①④):详情顶部新块,仅 reviewHint 命中时显示 =====
|
|
1227
|
+
// 四问动线 + 来源徽标,素材全用现有字段纯展示拼装(reviewHint 由 host 现算、get-tasks 透出):
|
|
1228
|
+
// ①为什么在这 → reviewHint.reasons(host 现算中文理由行)
|
|
1229
|
+
// ②改了什么 → deliverable.summary + deliverable.diff(git diff --stat 原文),标「Worker 汇报」来源徽标
|
|
1230
|
+
// ③机器怎么验的 → verification 结论(标「Verifier 实证」)+ 验收脚本 + 驳回/仲裁史(标「系统记录」)
|
|
1231
|
+
// ④怎么亲自确认 → verification.userTest 自测指南复用渲染,缺省显示 tier=internal 提示
|
|
1232
|
+
// 已阅(reviewedAt 非空)仍显示:已阅只收敛列表徽章/chip,详情块不消失——用户要真正过目,不能打开即闪退。
|
|
1233
|
+
function ReviewSummary(props) {
|
|
1234
|
+
var task = props.task
|
|
1235
|
+
var rh = task.reviewHint
|
|
1236
|
+
if (!rh || !(rh.score > 0)) return null
|
|
1237
|
+
function srcBadge(text, color) { return React.createElement('span', { style: { fontSize: 9, padding: '0 5px', borderRadius: 3, background: 'color-mix(in srgb, ' + color + ' 16%, transparent)', color: color, fontWeight: 600, flexShrink: 0 } }, text) }
|
|
1238
|
+
// ③ 驳回/仲裁史:history(歧义/裁决/驳回/干预/rejected)+ messages(rejection/arbitration)
|
|
1239
|
+
var histLines = []
|
|
1240
|
+
if (Array.isArray(task.history)) task.history.forEach(function (h) { if (h && h.note && /歧义|裁决|驳回|干预|rejected/i.test(h.note)) histLines.push('[' + (h.timestamp || '') + '] ' + String(h.note).slice(0, 160)) })
|
|
1241
|
+
if (Array.isArray(task.messages)) task.messages.forEach(function (m) { if (m && (m.kind === 'rejection' || m.kind === 'arbitration')) histLines.push('[' + (m.kind === 'rejection' ? '驳回' : '裁决') + '] ' + String(m.text || '').slice(0, 160)) })
|
|
1242
|
+
histLines = histLines.slice(0, 5)
|
|
1243
|
+
var q3rows = []
|
|
1244
|
+
if (task.verification) q3rows.push(React.createElement('div', { key: 'v', style: { display: 'flex', alignItems: 'flex-start', gap: 4, marginBottom: 2 } }, srcBadge('Verifier 实证', C.ok), React.createElement('span', { style: { fontSize: 11, color: C.text } }, (task.verification.verdict === 'approved' ? '验收通过' : '验收驳回') + (task.verification.summary ? ':' + task.verification.summary : ''))))
|
|
1245
|
+
if (task.acceptance) q3rows.push(React.createElement('div', { key: 'a', style: { display: 'flex', alignItems: 'flex-start', gap: 4, marginBottom: 2 } }, srcBadge('系统记录', C.text2), React.createElement('span', { style: { fontSize: 10, color: C.text2, fontFamily: 'monospace' } }, '验收脚本:' + task.acceptance)))
|
|
1246
|
+
if (histLines.length) q3rows.push(React.createElement('div', { key: 'h', style: { display: 'flex', alignItems: 'flex-start', gap: 4 } }, srcBadge('系统记录', C.text2), React.createElement('span', { style: { fontSize: 10, color: C.text2, whiteSpace: 'pre-wrap', lineHeight: 1.5 } }, '驳回/仲裁史:\n' + histLines.join('\n'))))
|
|
1247
|
+
// ④ 自测指南:复用现有 userTest 渲染口径,缺省 tier=internal 提示
|
|
1248
|
+
var q4
|
|
1249
|
+
var ut = task.verification && task.verification.userTest
|
|
1250
|
+
if (ut) {
|
|
1251
|
+
var tierMeta = ut.tier === 'ui' ? { c: C.ok, label: 'UI 可操作' } : (ut.tier === 'metric' ? { c: C.brand, label: '看指标' } : { c: C.text2, label: '纯内部' })
|
|
1252
|
+
q4 = React.createElement('div', { style: { marginBottom: 6 } },
|
|
1253
|
+
React.createElement('div', { style: { fontSize: 10, fontWeight: 600, color: C.text2, marginBottom: 2, display: 'flex', alignItems: 'center', gap: 4 } }, '④ 怎么亲自确认', srcBadge('自测指南 · ' + tierMeta.label, tierMeta.c)),
|
|
1254
|
+
ut.gist ? React.createElement('div', { style: { fontSize: 11, color: C.text, marginBottom: 2 } }, ut.gist) : null,
|
|
1255
|
+
ut.steps && ut.steps.length ? React.createElement('div', { style: { fontSize: 10, color: C.text } }, ut.steps.map(function (s, i) { return React.createElement('div', { key: i, style: { marginBottom: 1 } }, (i + 1) + '. ' + s) })) : null,
|
|
1256
|
+
ut.expect ? React.createElement('div', { style: { fontSize: 10, color: C.text2 } }, '预期:' + ut.expect) : null)
|
|
1257
|
+
} else {
|
|
1258
|
+
q4 = React.createElement('div', { style: { fontSize: 10, color: C.text2, lineHeight: 1.5 } }, '④ 怎么亲自确认:本卡无用户自测指南(tier=internal / 未附 userTest)——纯内部改动,验证靠测试套件')
|
|
1259
|
+
}
|
|
1260
|
+
return React.createElement('div', { id: 'tskb-review-summary', style: { marginBottom: 8, padding: '8px 10px', border: '1px solid ' + C.warn, borderRadius: 6, background: 'color-mix(in srgb, ' + C.warn + ' 7%, transparent)' } },
|
|
1261
|
+
React.createElement('div', { style: { fontSize: 12, fontWeight: 700, color: C.warn, marginBottom: 6, display: 'flex', alignItems: 'center', gap: 4 } }, ic('alert-triangle', 13), '待你过目 · 命中 ' + rh.score + ' 条风险信号'),
|
|
1262
|
+
React.createElement('div', { style: { marginBottom: 6 } },
|
|
1263
|
+
React.createElement('div', { style: { fontSize: 10, fontWeight: 600, color: C.text2, marginBottom: 2 } }, '① 为什么在这'),
|
|
1264
|
+
(rh.reasons || []).map(function (r, i) { return React.createElement('div', { key: i, style: { fontSize: 11, color: C.text, lineHeight: 1.5 } }, '· ' + r) })),
|
|
1265
|
+
React.createElement('div', { style: { marginBottom: 6 } },
|
|
1266
|
+
React.createElement('div', { style: { fontSize: 10, fontWeight: 600, color: C.text2, marginBottom: 2, display: 'flex', alignItems: 'center', gap: 4 } }, '② 改了什么', srcBadge('Worker 汇报', C.brand)),
|
|
1267
|
+
(task.deliverable && task.deliverable.summary) ? React.createElement('div', { style: { fontSize: 11, color: C.text, whiteSpace: 'pre-wrap', marginBottom: 2 } }, task.deliverable.summary) : React.createElement('div', { style: { fontSize: 10, color: C.text2, marginBottom: 2 } }, '(无开发描述)'),
|
|
1268
|
+
(task.deliverable && task.deliverable.diff) ? React.createElement('div', { style: { fontSize: 10, color: C.text2, whiteSpace: 'pre-wrap', maxHeight: 80, overflowY: 'auto', fontFamily: 'monospace' } }, task.deliverable.diff) : null),
|
|
1269
|
+
q3rows.length ? React.createElement('div', { style: { marginBottom: 6 } },
|
|
1270
|
+
React.createElement('div', { style: { fontSize: 10, fontWeight: 600, color: C.text2, marginBottom: 2 } }, '③ 机器怎么验的'),
|
|
1271
|
+
q3rows) : null,
|
|
1272
|
+
q4)
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
// 标签编辑(巡检三连①):逗号分隔 input,提交走 update-task 的 tags 通道(trim + 去空)
|
|
1276
|
+
function TagsEditor(props) {
|
|
1277
|
+
var task = props.task
|
|
1278
|
+
var _R = React; var useState = _R.useState
|
|
1279
|
+
var _s = useState((task.tags || []).join(', ')), draft = _s[0], setDraft = _s[1]
|
|
1280
|
+
function commit() {
|
|
1281
|
+
var tags = draft.split(/[,,]/).map(function (s) { return s.trim() }).filter(function (s) { return s.length > 0 })
|
|
1282
|
+
rpc('update-task', { taskId: task.id, tags: tags }).then(function () { fetchTasks() }).catch(function () {})
|
|
1283
|
+
}
|
|
1284
|
+
return React.createElement('div', { style: { marginBottom: 8, padding: '6px 8px', border: '1px solid ' + C.border, borderRadius: 6, background: C.card } },
|
|
1285
|
+
React.createElement('div', { style: { fontSize: 11, fontWeight: 600, color: C.text2, marginBottom: 4 } }, '🏷 标签(逗号分隔)'),
|
|
1286
|
+
React.createElement('div', { style: { display: 'flex', gap: 4 } },
|
|
1287
|
+
React.createElement('input', { value: draft, onChange: function (e) { setDraft(e.target.value) }, onKeyDown: function (e) { if (e.key === 'Enter') commit() }, placeholder: '如 bug, epic, 待验收', style: { flex: 1, minWidth: 0, fontSize: 11, padding: '4px 8px', border: '1px solid ' + C.border2, borderRadius: 4, background: C.card, color: C.text, fontFamily: 'inherit', boxSizing: 'border-box' } }),
|
|
1288
|
+
React.createElement('button', { onClick: commit, title: '保存标签(逗号分隔,自动 trim + 去空)', style: { fontSize: 10, padding: '3px 10px', border: 'none', borderRadius: 4, background: C.brand, color: C_INV, cursor: 'pointer', flexShrink: 0 } }, '保存')))
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1175
1291
|
function DetailView() {
|
|
1176
1292
|
var _R = React; var useState = _R.useState, useEffect = _R.useEffect; var task = getTask(state.detailId)
|
|
1177
1293
|
var _a = useState(task ? task.title : ''), editTitle = _a[0], setEditTitle = _a[1]; var _b = useState(task ? task.description || '' : ''), editDesc = _b[0], setEditDesc = _b[1]; var _c = useState(false), saving = _c[0], setSaving = _c[1]; var _d = useState(state.boardMode), mode = _d[0], setMode = _d[1]
|
|
1178
1294
|
var _e2 = useState(''), arbAnswer = _e2[0], setArbAnswer = _e2[1]; var _f2 = useState(''), interveneMsg = _f2[0], setInterveneMsg = _f2[1]; var _g2 = useState(''), actionMsg = _g2[0], setActionMsg = _g2[1]; var _h2 = useState('resume'), arbAction = _h2[0], setArbAction = _h2[1]
|
|
1179
1295
|
useEffect(function () { function update() { setMode(state.boardMode) }; listeners.push(update); update(); return function () { var i = listeners.indexOf(update); if (i >= 0) listeners.splice(i, 1) } }, [])
|
|
1296
|
+
// 已阅落账(异常驱动审视①③):打开命中且未阅的详情页即幂等 mark-reviewed——
|
|
1297
|
+
// host 幂等只写一次;本轮渲染仍按「未阅」展示审视摘要区(fetchTasks 回来前不闪退)。
|
|
1298
|
+
useEffect(function () { var t = getTask(state.detailId); if (t && t.reviewHint && t.reviewHint.score > 0 && !t.reviewedAt) { rpc('mark-reviewed', { taskId: t.id }).then(fetchTasks).catch(function () {}) } }, [state.detailId])
|
|
1180
1299
|
if (!task) { state.detailId = null; return React.createElement('div', { style: { padding: 20, color: C.text2 } }, '任务不存在') }
|
|
1181
1300
|
function doAction(fn) { fn().then(fetchTasks).catch(function () {}) }
|
|
1182
1301
|
function saveEdit() { setSaving(true); rpc('update-task', { taskId: task.id, title: editTitle, description: editDesc, resetToPending: true }).then(function () { setSaving(false); fetchTasks() }).catch(function () { setSaving(false) }) }
|
|
@@ -1227,7 +1346,9 @@ function apply(ctx) {
|
|
|
1227
1346
|
}
|
|
1228
1347
|
return React.createElement('div', { style: { padding: '4px 2px' } },
|
|
1229
1348
|
React.createElement('div', { onClick: function () { state.detailId = null; notify() }, style: { fontSize: 11, color: C.brand, cursor: 'pointer', marginBottom: 8 } }, '← 返回看板'),
|
|
1230
|
-
React.createElement(
|
|
1349
|
+
React.createElement(ReviewSummary, { task: task }),
|
|
1350
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 6, marginBottom: 8 } }, React.createElement('select', { value: task.priority || 'medium', onChange: function (e) { rpc('update-task', { taskId: task.id, priority: e.target.value }).then(fetchTasks).catch(function () {}) }, title: '优先级(四档:紧急/高/中/低)', style: { fontSize: 10, padding: '1px 4px', border: '1px solid ' + C.border, borderRadius: 3, background: C.card, color: (prioColor[task.priority] || prioColor.low), fontWeight: 600 } }, ['critical', 'high', 'medium', 'low'].map(function (p) { return React.createElement('option', { key: p, value: p }, prioLabel[p] + '(' + p + ')') })), React.createElement('span', { style: { fontSize: 11, padding: '1px 8px', borderRadius: 3, background: C.nested, color: C.text } }, statusLabels[task.status] || task.status), React.createElement('select', { value: task.pipeline || 'full', onChange: function (e) { rpc('update-task', { taskId: task.id, pipeline: e.target.value }).then(fetchTasks).catch(function () {}) }, title: '管线档位', style: { fontSize: 10, padding: '1px 4px', border: '1px solid ' + C.border, borderRadius: 3, background: C.card, color: C.text2 } }, React.createElement('option', { value: 'full' }, '全流程(执行+验证)'), React.createElement('option', { value: 'work' }, '免验证(只做不验)'), React.createElement('option', { value: 'direct' }, '主窗口处理')), task.pipelineAuto ? React.createElement('span', { style: { fontSize: 9, color: C.text2 }, title: '由规则自动分类,可手动覆盖' }, 'auto') : null, isManual ? React.createElement('span', { style: { fontSize: 10, color: C.text2, display: 'inline-flex', alignItems: 'center', gap: 2 } }, ic('user', 10), '手动派发') : null),
|
|
1351
|
+
React.createElement(TagsEditor, { key: task.id, task: task }),
|
|
1231
1352
|
// 流转到按钮组(键盘可达的状态迁移入口,替代拖拽;无合法迁移的状态(draft/resolved 等)整块不渲染)
|
|
1232
1353
|
flowBtns.length > 0 ? React.createElement('div', { role: 'group', 'aria-label': '状态流转', style: { display: 'flex', alignItems: 'center', gap: 5, flexWrap: 'wrap', marginBottom: 8, padding: '5px 8px', border: '1px solid ' + C.border, borderRadius: 6, background: C.card } },
|
|
1233
1354
|
React.createElement('span', { style: { fontSize: 10, fontWeight: 600, color: C.text2 } }, '⇄ 流转到:'),
|
|
@@ -1325,6 +1446,14 @@ function apply(ctx) {
|
|
|
1325
1446
|
ut.steps && ut.steps.length ? React.createElement('div', { style: { fontSize: 10, color: C.text, marginBottom: 3 } }, ut.steps.map(function (s, i) { return React.createElement('div', { key: i, style: { marginBottom: 1 } }, (i + 1) + '. ' + s) })) : null,
|
|
1326
1447
|
ut.expect ? React.createElement('div', { style: { fontSize: 10, color: C.text2 } }, '预期:' + ut.expect) : null)
|
|
1327
1448
|
})() : null) : null,
|
|
1449
|
+
// ===== 迟到 Verifier 结论块(验收时序三洞 ③):verifier 结论落到已落定卡后的留痕展示 =====
|
|
1450
|
+
// 主窗口抢批/归档早于 Verifier 结算时,其独立结论落 t.lateVerdict 而非静默丢弃——此处整块展示
|
|
1451
|
+
// (verdict/summary/checks + note 说明 + 时间来源),与上方正常 verification 块并列可对照。
|
|
1452
|
+
task.lateVerdict ? React.createElement('div', { style: { marginBottom: 8, padding: '6px 8px', border: '1px solid ' + (task.lateVerdict.verdict === 'approved' ? C.ok : C.err), borderRadius: 6, background: 'color-mix(in srgb, ' + (task.lateVerdict.verdict === 'approved' ? C.ok : C.err) + ' 8%, transparent)' } },
|
|
1453
|
+
React.createElement('div', { style: { fontSize: 11, fontWeight: 700, color: task.lateVerdict.verdict === 'approved' ? C.ok : C.err, marginBottom: 4, display: 'flex', alignItems: 'center', gap: 4 } }, ic('alert-triangle', 12), '迟到 Verifier 结论 · ' + (task.lateVerdict.by || '') + ' · ' + ago(task.lateVerdict.at)),
|
|
1454
|
+
React.createElement('div', { style: { fontSize: 10, color: C.text2, marginBottom: 4 } }, String(task.lateVerdict.note || '')),
|
|
1455
|
+
React.createElement('div', { style: { fontSize: 11, color: C.text, marginBottom: 4, whiteSpace: 'pre-wrap' } }, (task.lateVerdict.verdict === 'approved' ? '验收通过' : '验收驳回') + (task.lateVerdict.summary ? ':' + task.lateVerdict.summary : '')),
|
|
1456
|
+
task.lateVerdict.checks ? React.createElement('div', { style: { marginTop: 4 } }, React.createElement('div', { style: { fontSize: 10, fontWeight: 600, color: C.text2 } }, '核对项'), React.createElement('div', { style: { fontSize: 10, color: C.text2, whiteSpace: 'pre-wrap', maxHeight: 120, overflowY: 'auto' } }, task.lateVerdict.checks)) : null) : null,
|
|
1328
1457
|
React.createElement(MsgThread, { messages: task.messages, taskId: task.id }),
|
|
1329
1458
|
Array.isArray(task.history) && task.history.length > 0 ? React.createElement('div', { style: { marginBottom: 8 } }, React.createElement('div', { style: { fontSize: 11, fontWeight: 600, color: C.text2, marginBottom: 3 } }, '流转轨迹'), React.createElement('div', { style: { fontSize: 10, color: C.text2, padding: '4px 6px', background: C.nested, borderRadius: 4 } }, task.history.map(function (h, i) { return React.createElement('div', { key: i, style: { marginBottom: 2 } }, React.createElement('span', { style: { color: C.brand } }, statusLabels[h.to] || h.to), ' · ' + ago(h.timestamp) + ' · ', React.createElement(ActorLink, { id: h.actor }), h.note ? ' · ' + h.note : '') }))) : null,
|
|
1330
1459
|
React.createElement('div', { style: { display: 'flex', gap: 4, flexWrap: 'wrap', marginTop: 6 } },
|
|
@@ -1337,6 +1466,8 @@ function apply(ctx) {
|
|
|
1337
1466
|
task.status === 'verifying' ? React.createElement('button', { onClick: function () { doAction(function () { return rpc('verify-task', { taskId: task.id, verdict: 'approved' }) }) }, style: { fontSize: 10, padding: '3px 8px', border: 'none', borderRadius: 3, background: C.ok, color: C_INV, cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 3 } }, ic('check-circle', 10), '通过') : null,
|
|
1338
1467
|
task.status === 'verifying' ? React.createElement('button', { onClick: function () { var r = window.prompt('驳回原因:'); doAction(function () { return rpc('verify-task', { taskId: task.id, verdict: 'rejected', comment: r || '' }) }) }, style: { fontSize: 10, padding: '3px 8px', border: 'none', borderRadius: 3, background: C.err, color: C_INV, cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 3 } }, ic('x-circle', 10), '驳回') : null,
|
|
1339
1468
|
task.status === 'resolved' ? React.createElement('button', { onClick: function () { doAction(function () { return rpc('archive-task', { taskId: task.id }) }) }, style: { fontSize: 10, padding: '3px 8px', border: 'none', borderRadius: 3, background: C.text2, color: C_INV, cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 3 } }, ic('archive', 10), '归档') : null),
|
|
1469
|
+
// 取消(仅草稿/待办/阻塞):confirm 预警「取消后不再派发,可在归档恢复」;与删除并列但语义=作废留档(可恢复)
|
|
1470
|
+
canCancel(task) ? React.createElement('button', { onClick: function () { cancelTask(task.id, task.title, setActionMsg) }, title: '取消任务(取消后不再派发,可在归档恢复待办)', style: { fontSize: 10, padding: '3px 8px', border: '1px solid ' + C.warn, borderRadius: 3, background: 'transparent', color: C.warn, cursor: 'pointer', fontWeight: 600, display: 'inline-flex', alignItems: 'center', gap: 3 } }, ic('ban', 10), '取消') : null,
|
|
1340
1471
|
// 删除(仅草稿/待办/阻塞):confirm 防误删;被 host 门禁拒绝时把原因回显到详情页 actionMsg
|
|
1341
1472
|
// 容器 div 的收尾括号已挂在「归档」行;删除按钮作为**额外一行元素**排在它后面
|
|
1342
1473
|
// (多一个实参给同一个 createElement,属于合法调用),因此本行自身必须配平。
|
|
@@ -1585,7 +1716,7 @@ function apply(ctx) {
|
|
|
1585
1716
|
function batchDigest() {
|
|
1586
1717
|
var now = Date.now(), since = now - 86400000
|
|
1587
1718
|
var all = (Array.isArray(state.tasks) ? state.tasks : []).concat(Array.isArray(state.archived) ? state.archived : [])
|
|
1588
|
-
var resolved = 0, rejected = 0, tokTot = 0, tokEff = 0, timeouts = 0, trendResolved = 0, trendFirstPass = 0
|
|
1719
|
+
var resolved = 0, rejected = 0, tokTot = 0, tokEff = 0, timeouts = 0, trendResolved = 0, trendFirstPass = 0, reviewPending = 0
|
|
1589
1720
|
function in24(iso) { var d = Date.parse(iso); return isFinite(d) && d >= since && d <= now }
|
|
1590
1721
|
function pos(v) { var n = Number(v); return isFinite(n) && n > 0 ? n : 0 }
|
|
1591
1722
|
all.forEach(function (t) {
|
|
@@ -1604,13 +1735,15 @@ function apply(ctx) {
|
|
|
1604
1735
|
tokTot += tot; tokEff += inp + outp + cw
|
|
1605
1736
|
if (r.outcome === 'timeout/error' || r.outcome === 'timeout') timeouts++
|
|
1606
1737
|
})
|
|
1738
|
+
// ===== 未过目计数(异常驱动审视①⑤):命中风险信号且未阅的已完成/归档卡 =====
|
|
1739
|
+
if (reviewPending(t)) reviewPending++
|
|
1607
1740
|
})
|
|
1608
1741
|
var ghosts = 0
|
|
1609
1742
|
var hints = Array.isArray(state.healthHints) ? state.healthHints : []
|
|
1610
1743
|
hints.forEach(function (h) { var m = h && h.text ? String(h.text).match(/本轮回收 (\d+) 个幽灵活跃表项/) : null; if (m) ghosts += pos(m[1]) })
|
|
1611
1744
|
var trend = trendResolved > 0 ? ('一次通过率 ' + Math.round(trendFirstPass / trendResolved * 100) + '%(近 24h 完成 ' + trendResolved + ' 张中 ' + trendFirstPass + ' 张零驳回)') : '近 24h 无完成记录,暂无质量趋势'
|
|
1612
1745
|
var has = resolved > 0 || rejected > 0 || tokTot > 0 || timeouts > 0 || ghosts > 0 || trendResolved > 0
|
|
1613
|
-
return { has: has, resolved: resolved, rejected: rejected, tokTot: tokTot, tokEff: tokEff, timeouts: timeouts, ghosts: ghosts, trend: trend }
|
|
1746
|
+
return { has: has, resolved: resolved, rejected: rejected, tokTot: tokTot, tokEff: tokEff, timeouts: timeouts, ghosts: ghosts, trend: trend, reviewPending: reviewPending }
|
|
1614
1747
|
}
|
|
1615
1748
|
|
|
1616
1749
|
function buildReport() {
|
|
@@ -1624,10 +1757,12 @@ function apply(ctx) {
|
|
|
1624
1757
|
lines.push('- Token:约 ' + fmtTokens(dg.tokTot) + (dg.tokEff > 0 ? '(其中有效 ' + fmtTokens(dg.tokEff) + ')' : ''))
|
|
1625
1758
|
lines.push('- 异常事件:超时 ' + dg.timeouts + ' 次 · 幽灵回收 ' + dg.ghosts + ' 次')
|
|
1626
1759
|
lines.push('- 质量趋势:' + dg.trend)
|
|
1760
|
+
lines.push('- 未过目:' + dg.reviewPending + ' 张')
|
|
1627
1761
|
lines.push('')
|
|
1628
1762
|
} else {
|
|
1629
1763
|
lines.push('## 批次摘要(近 24 小时)')
|
|
1630
1764
|
lines.push('近 24 小时无活动记录(无完成 / 驳回 / 消耗 / 异常事件)')
|
|
1765
|
+
lines.push('- 未过目:' + dg.reviewPending + ' 张')
|
|
1631
1766
|
lines.push('')
|
|
1632
1767
|
}
|
|
1633
1768
|
var groups = { inProgress: [], verifying: [], pending: [], blocked: [], resolved: [] }
|
|
@@ -1953,13 +2088,14 @@ function apply(ctx) {
|
|
|
1953
2088
|
// 近 14 天柱形通用画法(① 通过率 / ③ 超时率共用):cells = [{ k, rate(null=当日无样本), tip }]
|
|
1954
2089
|
// 今日柱 brand 高亮 + 率值顶标;无样本日画 3px 灰基线(title 写明无样本,不假装 0%)。
|
|
1955
2090
|
function qtDayBars(cells, color, todayKey) {
|
|
1956
|
-
return React.createElement('div', { style: { display: 'flex', alignItems: 'flex-end', gap: 3, height: 46 } },
|
|
2091
|
+
return React.createElement('div', { style: { display: 'flex', alignItems: 'flex-end', gap: 3, height: 46, minWidth: 0, overflow: 'hidden' } },
|
|
1957
2092
|
cells.map(function (c) {
|
|
1958
2093
|
var isToday = c.k === todayKey
|
|
1959
2094
|
var h = c.rate === null ? 3 : Math.max(4, Math.round(c.rate * 34))
|
|
2095
|
+
h = Math.min(22, h) // clamp 柱高:100% 柱封顶 22px——46px 绘图容器扣掉上下率值/日期标签与 gap/描边后不越出,防单日 100% 黑柱向上盖住右列
|
|
1960
2096
|
var kp = c.k.split('-')
|
|
1961
2097
|
var dLabel = Number(kp[1]) + '/' + Number(kp[2])
|
|
1962
|
-
return React.createElement('div', { key: c.k, style: { flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'flex-end', gap: 1 }, title: c.tip },
|
|
2098
|
+
return React.createElement('div', { key: c.k, style: { flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'flex-end', gap: 1 }, title: c.tip },
|
|
1963
2099
|
c.rate !== null ? React.createElement('span', { style: { fontSize: 8, color: isToday ? C.brand : C.text2 } }, mpPct(c.rate)) : null,
|
|
1964
2100
|
React.createElement('div', { style: { width: '100%', maxWidth: 22, height: h + 'px', borderRadius: 2, background: c.rate === null ? C.nested : (isToday ? C.brand : color), border: '1px solid ' + (isToday ? C.brand : (c.rate === null ? C.border : color)) } }),
|
|
1965
2101
|
React.createElement('span', { style: { fontSize: 8, color: isToday ? C.brand : C.text2 } }, dLabel))
|
|
@@ -2004,7 +2140,7 @@ function apply(ctx) {
|
|
|
2004
2140
|
})
|
|
2005
2141
|
var fpHas = false; fpCells.forEach(function (c) { if (c.rate !== null) fpHas = true })
|
|
2006
2142
|
var fpToday = fpCells[fpCells.length - 1]
|
|
2007
|
-
var block1 = React.createElement('div', { style: { flex: '1 1 260px', minWidth: 230 } },
|
|
2143
|
+
var block1 = React.createElement('div', { style: { flex: '1 1 260px', minWidth: 230, overflow: 'hidden' } },
|
|
2008
2144
|
blockHead('一次通过率(近 14 天)', React.createElement('span', null, '今日 ', React.createElement('span', { style: { color: fpToday.rate !== null ? C.brand : C.text2, fontWeight: 600 } }, fpToday.rate !== null ? mpPct(fpToday.rate) : '—')), '一次通过率 = 当天落定「已完成」任务里零驳回(rejectCount=0)的占比;驳回 = Verifier 验收判 reject'),
|
|
2009
2145
|
fpHas ? qtDayBars(fpCells, C.ok, todayKey)
|
|
2010
2146
|
: React.createElement('div', { style: { fontSize: 10, color: C.text2 } }, '近 14 天窗口内暂无 resolved 任务落定'))
|
|
@@ -2032,7 +2168,7 @@ function apply(ctx) {
|
|
|
2032
2168
|
})
|
|
2033
2169
|
var toHas = false; toCells.forEach(function (c) { if (c.rate !== null) toHas = true })
|
|
2034
2170
|
var toToday = toCells[toCells.length - 1]
|
|
2035
|
-
var block3 = React.createElement('div', { style: { flex: '1 1 260px', minWidth: 230 } },
|
|
2171
|
+
var block3 = React.createElement('div', { style: { flex: '1 1 260px', minWidth: 230, overflow: 'hidden' } },
|
|
2036
2172
|
blockHead('超时率走势(近 14 天)', React.createElement('span', null, '今日 ', React.createElement('span', { style: { color: toToday.rate !== null ? C.warn : C.text2, fontWeight: 600 } }, toToday.rate !== null ? mpPct(toToday.rate) : '—')), '超时率 = 超时落定(timeout/error)的 run 占当天 run 的比例'),
|
|
2037
2173
|
toHas ? qtDayBars(toCells, C.warn, todayKey)
|
|
2038
2174
|
: React.createElement('div', { style: { fontSize: 10, color: C.text2 } }, '近 14 天窗口内暂无 run 落定'))
|
|
@@ -2109,7 +2245,7 @@ function apply(ctx) {
|
|
|
2109
2245
|
function HealthHints() {
|
|
2110
2246
|
var hints = (Array.isArray(state.healthHints) ? state.healthHints : []).filter(function (h) { return h && h.text })
|
|
2111
2247
|
if (!hints.length) return null
|
|
2112
|
-
//
|
|
2248
|
+
// 三级配色:err=🚨 红(C.err,数据安全红线);warn=⚠️ 琥珀(C.warn);info=ℹ️ 蓝灰(DSH 现有 business 信息色)
|
|
2113
2249
|
var infoColor = 'var(--dsw-alias-state-business-primary)'
|
|
2114
2250
|
return React.createElement('div', { style: { padding: '8px 10px', background: C.card, border: '1px solid ' + C.border, borderRadius: 6, marginBottom: 12 } },
|
|
2115
2251
|
React.createElement('div', { style: { fontSize: 11, fontWeight: 600, color: C.text2, marginBottom: 6, display: 'flex', alignItems: 'center', gap: 4 } }, ic('activity', 11), '架构健康'),
|
|
@@ -2117,9 +2253,10 @@ function apply(ctx) {
|
|
|
2117
2253
|
React.createElement('div', { style: { fontSize: 9, color: C.text2, marginBottom: 5, lineHeight: 1.5 } }, '运行时自检告警,多为自动恢复的瞬时异常。术语:settle = 任务结算通道;touches = 任务声明的改动文件(文件锁);p90 = 执行时长第 90 百分位'),
|
|
2118
2254
|
hints.map(function (h, i) {
|
|
2119
2255
|
var warn = h.level === 'warn'
|
|
2120
|
-
var
|
|
2256
|
+
var err = h.level === 'err'
|
|
2257
|
+
var col = err ? C.err : (warn ? C.warn : infoColor)
|
|
2121
2258
|
return React.createElement('div', { key: i, style: { display: 'flex', alignItems: 'baseline', gap: 5, fontSize: 10, lineHeight: 1.5, color: col, marginBottom: 3 } },
|
|
2122
|
-
React.createElement('span', { style: { flexShrink: 0 } }, warn ? '⚠️' : 'ℹ️'),
|
|
2259
|
+
React.createElement('span', { style: { flexShrink: 0 } }, err ? '🚨' : (warn ? '⚠️' : 'ℹ️')),
|
|
2123
2260
|
React.createElement('span', null, String(h.text)))
|
|
2124
2261
|
}))
|
|
2125
2262
|
}
|