feat(web): SVG 图标集替换 Unicode —— tsk_3uhHOGU0qzj0

用 design/assets/icons/ 的 15 个双色 SVG 图标替换现网纯 Unicode 图标:

• 拷贝 design/assets/icons/*.svg → web/assets/icons/(15 个)
• app.js 顶部新增 ICONS 对象 + icon() 内联 helper
• index.html:徽章组(gate/ready/running/deps)、配置按钮(config)、
  同步按钮(sync)、搜索输入框(search + filter-search-wrap 包装)、
  清除筛选按钮(close)、「新建」/「新建任务」按钮(add)
  各区块头加语义图标:project/task/gate/running/flow
• app.js 模板字符串:⛓→deps、✕→close(任务行取消/预览关闭/归档关闭)、
  依赖等待标签、ap-reason→reason SVG、分支显示→git、事件项 ev-type→event
• style.css:新增 svg.icon 基础尺寸规则、filter-search-wrap 布局、
  各语义上下文 --icon-accent 注入(暗/浅主题均用 CSS 变量自动适配)、
  .ap-reason::before 移除 content:'↳' 改用 SVG
• 品牌字符保留不动:▍▮→·— 及状态 CSS 圆点

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-13 15:08:53 +08:00
parent f020e15137
commit 1c4a81b3f3
18 changed files with 100 additions and 30 deletions
+33 -12
View File
@@ -3,6 +3,27 @@
════════════════════════════════════════════════════════════ */ ════════════════════════════════════════════════════════════ */
'use strict'; 'use strict';
// ── SVG 图标集(v1.1 双色调,24×242px 圆头;currentColor=基础描边,--icon-accent=信号色) ──
const ICONS = {
task: `<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="4" y="4" width="16" height="16" rx="2.5"/><polyline points="8 12 11 15 16 9" stroke="var(--icon-accent,#5fdd7d)"/></svg>`,
project: `<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 7a2 2 0 0 1 2-2h4l2 3h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z"/></svg>`,
gate: `<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3L22 21L2 21Z"/><line x1="12" y1="10" x2="12" y2="14" stroke="var(--icon-accent,#b88ef5)"/><line x1="12" y1="17.5" x2="12" y2="17.51" stroke="var(--icon-accent,#b88ef5)"/></svg>`,
ready: `<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 4L19 12L7 20Z" stroke="var(--icon-accent,#5fdd7d)"/></svg>`,
running: `<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="8"/><circle cx="12" cy="12" r="3" fill="var(--icon-accent,#59c8d8)" stroke="none"/></svg>`,
sync: `<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"/><path d="M21 3v5h-5" stroke="var(--icon-accent,#5fdd7d)"/></svg>`,
config: `<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="3" y1="6" x2="12" y2="6"/><circle cx="15" cy="6" r="2.5" stroke="var(--icon-accent,#5fdd7d)"/><line x1="18" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="5.5" y2="12"/><circle cx="9" cy="12" r="2.5" stroke="var(--icon-accent,#5fdd7d)"/><line x1="12.5" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="12.5" y2="18"/><circle cx="16" cy="18" r="2.5" stroke="var(--icon-accent,#5fdd7d)"/><line x1="19.5" y1="18" x2="21" y2="18"/></svg>`,
search: `<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="10.5" cy="10.5" r="6.5"/><line x1="15.5" y1="15.5" x2="21" y2="21" stroke="var(--icon-accent,#5fdd7d)"/></svg>`,
git: `<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="6" y1="3" x2="6" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9" stroke="var(--icon-accent,#5fdd7d)"/></svg>`,
deps: `<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/><line x1="10" y1="10" x2="14" y2="14" stroke="var(--icon-accent,#f0b429)"/></svg>`,
reason: `<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="5 4 5 14 18 14"/><polyline points="13 9 18 14 13 19" stroke="var(--icon-accent,#f0b429)"/></svg>`,
event: `<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="2 12 7 12 10 5 14 19 17 12 22 12" stroke="var(--icon-accent,#59c8d8)"/></svg>`,
flow: `<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="4" y1="12" x2="19" y2="12"/><polyline points="13 6 19 12 13 18" stroke="var(--icon-accent,#59c8d8)"/></svg>`,
add: `<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="12" y1="4" x2="12" y2="20" stroke="var(--icon-accent,#5fdd7d)"/><line x1="4" y1="12" x2="20" y2="12" stroke="var(--icon-accent,#5fdd7d)"/></svg>`,
close: `<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="5" y1="5" x2="19" y2="19"/><line x1="19" y1="5" x2="5" y2="19"/></svg>`,
};
/** 返回指定图标的内联 SVG HTML(name 不存在时返回空串) */
function icon(name) { return ICONS[name] || ''; }
// ── 模型常量(与 src/model/status.ts / complexity.ts 对齐) ── // ── 模型常量(与 src/model/status.ts / complexity.ts 对齐) ──
const STATUS_LABEL = { const STATUS_LABEL = {
init: '新建', analyzing: '分析拆解中', plan_review: '待确认拆解', init: '新建', analyzing: '分析拆解中', plan_review: '待确认拆解',
@@ -461,7 +482,7 @@ function gateDocs(t) {
blocks.push(reviewReportBlocks(r)); // CODE REVIEW + 安全审计 两节(各带 verdict 徽章),置于 DIFF 摘要上方 blocks.push(reviewReportBlocks(r)); // CODE REVIEW + 安全审计 两节(各带 verdict 徽章),置于 DIFF 摘要上方
blocks.push(`<div class="gate-doc-label">RESULT · 执行结果</div> blocks.push(`<div class="gate-doc-label">RESULT · 执行结果</div>
<dl class="result-kv"> <dl class="result-kv">
${r.branch ? `<dt>分支</dt><dd>${esc(r.branch)}</dd>` : ''} ${r.branch ? `<dt>${icon('git')} 分支</dt><dd>${esc(r.branch)}</dd>` : ''}
${r.worktree ? `<dt>worktree</dt><dd>${esc(r.worktree)}</dd>` : ''} ${r.worktree ? `<dt>worktree</dt><dd>${esc(r.worktree)}</dd>` : ''}
${r.prUrl ? `<dt>PR</dt><dd><a href="${esc(r.prUrl)}" target="_blank">${esc(r.prUrl)}</a></dd>` : ''} ${r.prUrl ? `<dt>PR</dt><dd><a href="${esc(r.prUrl)}" target="_blank">${esc(r.prUrl)}</a></dd>` : ''}
</dl>`); </dl>`);
@@ -699,7 +720,7 @@ function renderArchiveModal(t, runs, events) {
const resultBlock = r ? `${reviewReportBlocks(r)} const resultBlock = r ? `${reviewReportBlocks(r)}
<div class="gate-doc-label">执行结果</div> <div class="gate-doc-label">执行结果</div>
<dl class="result-kv"> <dl class="result-kv">
${r.branch ? kv('分支', esc(r.branch)) : ''} ${r.branch ? kv(`${icon('git')} 分支`, esc(r.branch)) : ''}
${r.worktree ? kv('worktree', esc(r.worktree)) : ''} ${r.worktree ? kv('worktree', esc(r.worktree)) : ''}
${r.prUrl ? kv('合并', esc(r.prUrl)) : ''} ${r.prUrl ? kv('合并', esc(r.prUrl)) : ''}
${r.commits && r.commits.length ? kv('commits', r.commits.map(esc).join('<br>')) : ''} ${r.commits && r.commits.length ? kv('commits', r.commits.map(esc).join('<br>')) : ''}
@@ -709,7 +730,7 @@ function renderArchiveModal(t, runs, events) {
const approvalRows = t.approvals && t.approvals.length ? `<div class="gate-doc-label">审批记录</div> const approvalRows = t.approvals && t.approvals.length ? `<div class="gate-doc-label">审批记录</div>
<dl class="result-kv">${t.approvals.map((a) => kv( <dl class="result-kv">${t.approvals.map((a) => kv(
`${GATE_LABEL[a.gate] || esc(a.gate)} · ${a.action === 'accept' ? '接受' : '驳回'}`, `${GATE_LABEL[a.gate] || esc(a.gate)} · ${a.action === 'accept' ? '接受' : '驳回'}`,
`审批人 ${esc(a.actor)} · ${fmtFull(a.at)}${a.reason ? `<br>意见:${esc(a.reason)}` : ''}`, `审批人 ${esc(a.actor)} · ${fmtFull(a.at)}${a.reason ? `<br>${icon('reason')} 意见:${esc(a.reason)}` : ''}`,
)).join('')}</dl>` : ''; )).join('')}</dl>` : '';
const timeline = events.length ? `<div class="gate-doc-label">状态流转时间线</div> const timeline = events.length ? `<div class="gate-doc-label">状态流转时间线</div>
@@ -739,7 +760,7 @@ function renderArchiveModal(t, runs, events) {
${cplxBadge(t.complexity)} ${cplxBadge(t.complexity)}
${statusChip(t.status)} ${statusChip(t.status)}
<span class="spacer"></span> <span class="spacer"></span>
<button class="btn btn-ghost" data-action="archive-modal-close" title="关闭(Esc"></button> <button class="btn btn-ghost" data-action="archive-modal-close" title="关闭(Esc">${icon('close')}</button>
</div> </div>
<div class="preview-body"> <div class="preview-body">
${attrs}${docs}${runRows}${resultBlock}${approvalRows}${timeline} ${attrs}${docs}${runRows}${resultBlock}${approvalRows}${timeline}
@@ -804,7 +825,7 @@ function renderPreview() {
${cplxBadge(t.complexity)} ${cplxBadge(t.complexity)}
${statusChip(t.status)} ${statusChip(t.status)}
<span class="spacer"></span> <span class="spacer"></span>
<button class="btn btn-ghost" data-action="preview-close" title="关闭(Esc"></button> <button class="btn btn-ghost" data-action="preview-close" title="关闭(Esc">${icon('close')}</button>
</div> </div>
<div class="preview-body">${gateDocs(t)}</div> <div class="preview-body">${gateDocs(t)}</div>
<div class="preview-foot"> <div class="preview-foot">
@@ -1008,12 +1029,12 @@ function renderTree() {
if (t.status !== 'blocked' || !(t.deps || []).length) return ''; if (t.status !== 'blocked' || !(t.deps || []).length) return '';
const unmet = t.deps.filter((d) => byId.get(d)?.status !== 'done'); const unmet = t.deps.filter((d) => byId.get(d)?.status !== 'done');
if (!unmet.length) return ''; if (!unmet.length) return '';
return `<span class="t-deps-wait" title="等待依赖完成:${unmet.map((d) => esc(byId.get(d)?.title || d)).join('、')}"> 等依赖 ${unmet.length}</span>`; return `<span class="t-deps-wait" title="等待依赖完成:${unmet.map((d) => esc(byId.get(d)?.title || d)).join('、')}">${icon('deps')} 等依赖 ${unmet.length}</span>`;
})()} })()}
<span class="t-prio ${t.priority === 0 ? 'hot' : ''}">P${t.priority}</span> <span class="t-prio ${t.priority === 0 ? 'hot' : ''}">P${t.priority}</span>
${cplxBadge(t.complexity, t.id)} ${cplxBadge(t.complexity, t.id)}
${statusChip(t.status)} ${statusChip(t.status)}
${!ARCHIVED.has(t.status) ? `<button class="btn btn-ghost btn-xs task-row-cancel" data-action="row-cancel" data-id="${esc(t.id)}" title="取消任务(进入归档)"></button>` : ''} ${!ARCHIVED.has(t.status) ? `<button class="btn btn-ghost btn-xs task-row-cancel" data-action="row-cancel" data-id="${esc(t.id)}" title="取消任务(进入归档)">${icon('close')}</button>` : ''}
<button class="btn btn-ghost btn-xs task-row-delete" data-action="row-delete" data-id="${esc(t.id)}" title="彻底删除任务(不可恢复)">🗑</button> <button class="btn btn-ghost btn-xs task-row-delete" data-action="row-delete" data-id="${esc(t.id)}" title="彻底删除任务(不可恢复)">🗑</button>
</div> </div>
${expanded ? renderDetail(t) : ''} ${expanded ? renderDetail(t) : ''}
@@ -1054,7 +1075,7 @@ function renderDetail(t) {
<div>${reviewReportBlocks(r)} <div>${reviewReportBlocks(r)}
<div class="detail-label">RESULT · 执行结果</div> <div class="detail-label">RESULT · 执行结果</div>
<dl class="result-kv"> <dl class="result-kv">
<dt>分支</dt><dd>${esc(r.branch || '—')}</dd> <dt>${icon('git')} 分支</dt><dd>${esc(r.branch || '—')}</dd>
<dt>worktree</dt><dd>${esc(r.worktree || '—')}</dd> <dt>worktree</dt><dd>${esc(r.worktree || '—')}</dd>
${r.prUrl ? `<dt>合并</dt><dd>${esc(r.prUrl)}</dd>` : ''} ${r.prUrl ? `<dt>合并</dt><dd>${esc(r.prUrl)}</dd>` : ''}
${r.commits && r.commits.length ? `<dt>commits</dt><dd>${r.commits.map(esc).join('<br>')}</dd>` : ''} ${r.commits && r.commits.length ? `<dt>commits</dt><dd>${r.commits.map(esc).join('<br>')}</dd>` : ''}
@@ -1072,7 +1093,7 @@ function renderDetail(t) {
<span class="ap-act-${a.action}">${a.action === 'accept' ? '✓ 通过' : '✗ 驳回'}</span> <span class="ap-act-${a.action}">${a.action === 'accept' ? '✓ 通过' : '✗ 驳回'}</span>
<span class="ap-gate">${GATE_LABEL[a.gate] || esc(a.gate)} · ${esc(a.actor)}</span> <span class="ap-gate">${GATE_LABEL[a.gate] || esc(a.gate)} · ${esc(a.actor)}</span>
<span class="ap-time">${fmtTime(a.at)}</span> <span class="ap-time">${fmtTime(a.at)}</span>
${a.reason ? `<span class="ap-reason">${esc(a.reason)}</span>` : ''} ${a.reason ? `<span class="ap-reason">${icon('reason')}<span>${esc(a.reason)}</span></span>` : ''}
</div>`).join('')} </div>`).join('')}
</div>`); </div>`);
} }
@@ -1086,7 +1107,7 @@ function renderDetail(t) {
const dt = S.tasks.find((x) => x.id === d); const dt = S.tasks.find((x) => x.id === d);
const ok = dt && dt.status === 'done'; const ok = dt && dt.status === 'done';
return `<div class="dep-item ${ok ? 'ok' : 'wait'}" data-action="goto-task" data-id="${esc(d)}" title="点击定位到该任务"> return `<div class="dep-item ${ok ? 'ok' : 'wait'}" data-action="goto-task" data-id="${esc(d)}" title="点击定位到该任务">
<span class="dep-mark">${ok ? '✓' : '⛓'}</span> <span class="dep-mark">${ok ? '✓' : icon('deps')}</span>
${dt ? statusChip(dt.status) : ''} ${dt ? statusChip(dt.status) : ''}
<span class="dep-title">${esc(dt ? dt.title : d)}</span> <span class="dep-title">${esc(dt ? dt.title : d)}</span>
<span class="t-id">${esc(d.slice(-6))}</span> <span class="t-id">${esc(d.slice(-6))}</span>
@@ -1114,7 +1135,7 @@ function renderDetail(t) {
<button class="btn btn-xs" data-action="task-cancel-abort" data-id="${esc(t.id)}">算了</button> <button class="btn btn-xs" data-action="task-cancel-abort" data-id="${esc(t.id)}">算了</button>
</div>`; </div>`;
} }
return `<button class="btn btn-xs btn-reject" data-action="task-cancel" data-id="${esc(t.id)}" title="取消任务(进入 cancelled 状态,计入归档)"> 取消任务</button>`; return `<button class="btn btn-xs btn-reject" data-action="task-cancel" data-id="${esc(t.id)}" title="取消任务(进入 cancelled 状态,计入归档)">${icon('close')} 取消任务</button>`;
})() : ''; })() : '';
const deleteSection = (() => { const deleteSection = (() => {
if (S.confirmDelete === t.id) { if (S.confirmDelete === t.id) {
@@ -1214,7 +1235,7 @@ function renderEvents() {
ul.innerHTML = S.events.slice(0, 80).map((e) => ` ul.innerHTML = S.events.slice(0, 80).map((e) => `
<li class="${EVENT_CLASS[e.type] || ''}"> <li class="${EVENT_CLASS[e.type] || ''}">
<div class="ev-head"> <div class="ev-head">
<span class="ev-type">${EVENT_LABEL[e.type] || esc(e.type)}</span> <span class="ev-type">${icon('event')}${EVENT_LABEL[e.type] || esc(e.type)}</span>
<span class="ev-time">${fmtTime(e.at)}</span> <span class="ev-time">${fmtTime(e.at)}</span>
</div> </div>
<div class="ev-detail">${eventDetail(e)}</div> <div class="ev-detail">${eventDetail(e)}</div>
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="4" x2="12" y2="20" stroke="var(--icon-accent, #5fdd7d)"></line><line x1="4" y1="12" x2="20" y2="12" stroke="var(--icon-accent, #5fdd7d)"></line></svg>

After

Width:  |  Height:  |  Size: 326 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="5" x2="19" y2="19"></line><line x1="19" y1="5" x2="5" y2="19"></line></svg>

After

Width:  |  Height:  |  Size: 250 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="6" x2="12" y2="6"></line><circle cx="15" cy="6" r="2.5" stroke="var(--icon-accent, #5fdd7d)"></circle><line x1="18" y1="6" x2="21" y2="6"></line><line x1="3" y1="12" x2="5.5" y2="12"></line><circle cx="9" cy="12" r="2.5" stroke="var(--icon-accent, #5fdd7d)"></circle><line x1="12.5" y1="12" x2="21" y2="12"></line><line x1="3" y1="18" x2="12.5" y2="18"></line><circle cx="16" cy="18" r="2.5" stroke="var(--icon-accent, #5fdd7d)"></circle><line x1="19.5" y1="18" x2="21" y2="18"></line></svg>

After

Width:  |  Height:  |  Size: 666 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7" rx="1.5"></rect><rect x="14" y="14" width="7" height="7" rx="1.5"></rect><line x1="10" y1="10" x2="14" y2="14" stroke="var(--icon-accent, #f0b429)"></line></svg>

After

Width:  |  Height:  |  Size: 358 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="2 12 7 12 10 5 14 19 17 12 22 12" stroke="var(--icon-accent, #59c8d8)"></polyline></svg>

After

Width:  |  Height:  |  Size: 264 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="4" y1="12" x2="19" y2="12"></line><polyline points="13 6 19 12 13 18" stroke="var(--icon-accent, #59c8d8)"></polyline></svg>

After

Width:  |  Height:  |  Size: 292 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3 L22 21 L2 21 Z"></path><line x1="12" y1="10" x2="12" y2="14" stroke="var(--icon-accent, #b88ef5)"></line><line x1="12" y1="17.5" x2="12" y2="17.51" stroke="var(--icon-accent, #b88ef5)"></line></svg>

After

Width:  |  Height:  |  Size: 371 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="6" y1="3" x2="6" y2="15"></line><circle cx="18" cy="6" r="3"></circle><circle cx="6" cy="18" r="3"></circle><path d="M18 9a9 9 0 0 1-9 9" stroke="var(--icon-accent, #5fdd7d)"></path></svg>

After

Width:  |  Height:  |  Size: 356 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7 a2 2 0 0 1 2-2 h4 l2 3 h8 a2 2 0 0 1 2 2 v8 a2 2 0 0 1-2 2 H5 a2 2 0 0 1-2-2 Z"></path></svg>

After

Width:  |  Height:  |  Size: 265 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 4 L19 12 L7 20 Z" stroke="var(--icon-accent, #5fdd7d)"></path></svg>

After

Width:  |  Height:  |  Size: 238 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="5 4 5 14 18 14"></polyline><polyline points="13 9 18 14 13 19" stroke="var(--icon-accent, #f0b429)"></polyline></svg>

After

Width:  |  Height:  |  Size: 293 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="8"></circle><circle cx="12" cy="12" r="3" fill="var(--icon-accent, #59c8d8)" stroke="none"></circle></svg>

After

Width:  |  Height:  |  Size: 291 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="10.5" cy="10.5" r="6.5"></circle><line x1="15.5" y1="15.5" x2="21" y2="21" stroke="var(--icon-accent, #5fdd7d)"></line></svg>

After

Width:  |  Height:  |  Size: 295 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"></path><path d="M21 3v5h-5" stroke="var(--icon-accent, #5fdd7d)"></path></svg>

After

Width:  |  Height:  |  Size: 296 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="4" width="16" height="16" rx="2.5"></rect><polyline points="8 12 11 15 16 9" stroke="var(--icon-accent, #5fdd7d)"></polyline></svg>

After

Width:  |  Height:  |  Size: 304 B

+18 -15
View File
@@ -20,8 +20,8 @@
</div> </div>
<div class="side-head"> <div class="side-head">
<span class="head-mark"></span>项目 <span class="head-mark"></span><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 7a2 2 0 0 1 2-2h4l2 3h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z"/></svg> 项目
<button class="btn btn-ghost btn-xs" data-action="open-new-project">+ 新建</button> <button class="btn btn-ghost btn-xs" data-action="open-new-project"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="12" y1="4" x2="12" y2="20" stroke="var(--icon-accent,#5fdd7d)"/><line x1="4" y1="12" x2="20" y2="12" stroke="var(--icon-accent,#5fdd7d)"/></svg> 新建</button>
</div> </div>
<ul id="projectList" class="project-list"></ul> <ul id="projectList" class="project-list"></ul>
@@ -41,23 +41,23 @@
<div class="spacer"></div> <div class="spacer"></div>
<div id="badgeGroup" class="badge-group" hidden> <div id="badgeGroup" class="badge-group" hidden>
<span id="bdgGate" class="bdg bdg-gate" title="等待你裁决的审批闸"> <span id="bdgGate" class="bdg bdg-gate" title="等待你裁决的审批闸">
<span class="bdg-ico"></span><span class="bdg-n">0</span><span class="bdg-label">待审批</span> <span class="bdg-ico"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3L22 21L2 21Z"/><line x1="12" y1="10" x2="12" y2="14" stroke="var(--icon-accent,#b88ef5)"/><line x1="12" y1="17.5" x2="12" y2="17.51" stroke="var(--icon-accent,#b88ef5)"/></svg></span><span class="bdg-n">0</span><span class="bdg-label">待审批</span>
</span> </span>
<span id="bdgReady" class="bdg bdg-ready" title="可执行(依赖已满足)+ 排队中"> <span id="bdgReady" class="bdg bdg-ready" title="可执行(依赖已满足)+ 排队中">
<span class="bdg-ico"></span><span class="bdg-n">0</span><span class="bdg-label">待执行</span> <span class="bdg-ico"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 4L19 12L7 20Z" stroke="var(--icon-accent,#5fdd7d)"/></svg></span><span class="bdg-n">0</span><span class="bdg-label">待执行</span>
</span> </span>
<span id="bdgRun" class="bdg bdg-run" title="agent 正在执行"> <span id="bdgRun" class="bdg bdg-run" title="agent 正在执行">
<span class="bdg-ico"></span><span class="bdg-n">0</span><span class="bdg-label">执行中</span> <span class="bdg-ico"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="8"/><circle cx="12" cy="12" r="3" fill="var(--icon-accent,#59c8d8)" stroke="none"/></svg></span><span class="bdg-n">0</span><span class="bdg-label">执行中</span>
</span> </span>
<span id="bdgBlocked" class="bdg bdg-blocked" title="被依赖阻塞(依赖完成后自动放行)"> <span id="bdgBlocked" class="bdg bdg-blocked" title="被依赖阻塞(依赖完成后自动放行)">
<span class="bdg-ico"></span><span class="bdg-n">0</span><span class="bdg-label">被阻塞</span> <span class="bdg-ico"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/><line x1="10" y1="10" x2="14" y2="14" stroke="var(--icon-accent,#f0b429)"/></svg></span><span class="bdg-n">0</span><span class="bdg-label">被阻塞</span>
</span> </span>
<span id="bdgTotal" class="bdg bdg-total" title="未完成任务总量(不含 done / 已拆解容器 / 取消)"> <span id="bdgTotal" class="bdg bdg-total" title="未完成任务总量(不含 done / 已拆解容器 / 取消)">
<span class="bdg-ico">Σ</span><span class="bdg-n">0</span><span class="bdg-label">总量</span> <span class="bdg-ico">Σ</span><span class="bdg-n">0</span><span class="bdg-label">总量</span>
</span> </span>
</div> </div>
<div id="topActions" class="top-actions" hidden> <div id="topActions" class="top-actions" hidden>
<button class="btn btn-xs" data-action="toggle-config" title="项目配置(并发 / 工作模式 / todo 同步)"> 配置</button> <button class="btn btn-xs" data-action="toggle-config" title="项目配置(并发 / 工作模式 / todo 同步)"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="3" y1="6" x2="12" y2="6"/><circle cx="15" cy="6" r="2.5" stroke="var(--icon-accent,#5fdd7d)"/><line x1="18" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="5.5" y2="12"/><circle cx="9" cy="12" r="2.5" stroke="var(--icon-accent,#5fdd7d)"/><line x1="12.5" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="12.5" y2="18"/><circle cx="16" cy="18" r="2.5" stroke="var(--icon-accent,#5fdd7d)"/><line x1="19.5" y1="18" x2="21" y2="18"/></svg> 配置</button>
</div> </div>
</header> </header>
@@ -92,25 +92,25 @@
</div> </div>
<div class="form-row cfg-sync-row"> <div class="form-row cfg-sync-row">
<span id="syncMeta" class="sync-meta"></span> <span id="syncMeta" class="sync-meta"></span>
<button id="btnSync" class="btn btn-xs" data-action="sync-todo"> 同步 todo</button> <button id="btnSync" class="btn btn-xs" data-action="sync-todo"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"/><path d="M21 3v5h-5" stroke="var(--icon-accent,#5fdd7d)"/></svg> 同步 todo</button>
</div> </div>
</section> </section>
<section id="agentSection" class="agent-section"> <section id="agentSection" class="agent-section">
<div class="sec-head"><span class="head-mark cyan"></span>Agent 执行<span id="agentTotal" class="agent-total"></span></div> <div class="sec-head"><span class="head-mark cyan"></span><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="8"/><circle cx="12" cy="12" r="3" fill="var(--icon-accent,#59c8d8)" stroke="none"/></svg> Agent 执行<span id="agentTotal" class="agent-total"></span></div>
<div id="agentBody"></div> <div id="agentBody"></div>
</section> </section>
<section id="gateSection" class="gate-section" hidden> <section id="gateSection" class="gate-section" hidden>
<div class="gate-stripe"></div> <div class="gate-stripe"></div>
<div class="sec-head gate-head"><span class="head-mark violet"></span>审批闸 · 等待裁决</div> <div class="sec-head gate-head"><span class="head-mark violet"></span><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3L22 21L2 21Z"/><line x1="12" y1="10" x2="12" y2="14" stroke="var(--icon-accent,#b88ef5)"/><line x1="12" y1="17.5" x2="12" y2="17.51" stroke="var(--icon-accent,#b88ef5)"/></svg> 审批闸 · 等待裁决</div>
<div id="gateList"></div> <div id="gateList"></div>
</section> </section>
<section id="taskSection"> <section id="taskSection">
<div class="sec-head"> <div class="sec-head">
<span class="head-mark"></span>任务树 <span class="head-mark"></span><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="4" y="4" width="16" height="16" rx="2.5"/><polyline points="8 12 11 15 16 9" stroke="var(--icon-accent,#5fdd7d)"/></svg> 任务树
<button class="btn btn-ghost btn-xs" data-action="toggle-new-task">+ 新建任务</button> <button class="btn btn-ghost btn-xs" data-action="toggle-new-task"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="12" y1="4" x2="12" y2="20" stroke="var(--icon-accent,#5fdd7d)"/><line x1="4" y1="12" x2="20" y2="12" stroke="var(--icon-accent,#5fdd7d)"/></svg> 新建任务</button>
</div> </div>
<form id="newTaskPanel" class="panel form-panel" hidden> <form id="newTaskPanel" class="panel form-panel" hidden>
@@ -147,11 +147,14 @@
<div id="filterBar" class="filter-bar" hidden> <div id="filterBar" class="filter-bar" hidden>
<div class="filter-row"> <div class="filter-row">
<input id="filterKw" type="text" placeholder="⌕ 搜索标题…" autocomplete="off"> <span class="filter-search-wrap">
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="10.5" cy="10.5" r="6.5"/><line x1="15.5" y1="15.5" x2="21" y2="21" stroke="var(--icon-accent,#5fdd7d)"/></svg>
<input id="filterKw" type="text" placeholder="搜索标题…" autocomplete="off">
</span>
<span id="filterCplx" class="fchips"></span> <span id="filterCplx" class="fchips"></span>
<span class="spacer"></span> <span class="spacer"></span>
<span id="filterCount" class="filter-count"></span> <span id="filterCount" class="filter-count"></span>
<button class="btn btn-ghost btn-xs" id="filterClear" data-action="filter-clear" hidden> 清除筛选</button> <button class="btn btn-ghost btn-xs" id="filterClear" data-action="filter-clear" hidden><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="5" y1="5" x2="19" y2="19"/><line x1="19" y1="5" x2="5" y2="19"/></svg> 清除筛选</button>
</div> </div>
<div id="filterStatus" class="filter-row"></div> <div id="filterStatus" class="filter-row"></div>
</div> </div>
@@ -175,7 +178,7 @@
<!-- ════════ 右栏:事件流 ════════ --> <!-- ════════ 右栏:事件流 ════════ -->
<aside id="eventPanel"> <aside id="eventPanel">
<div class="sec-head"><span class="head-mark"></span>事件流</div> <div class="sec-head"><span class="head-mark"></span><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="4" y1="12" x2="19" y2="12"/><polyline points="13 6 19 12 13 18" stroke="var(--icon-accent,#59c8d8)"/></svg> 事件流</div>
<ul id="eventList" class="event-list"></ul> <ul id="eventList" class="event-list"></ul>
</aside> </aside>
</div> </div>
+34 -3
View File
@@ -631,8 +631,8 @@ button.cplx-btn:hover { box-shadow: 0 0 8px rgba(216,228,212,.18); }
.ap-act-reject { color: var(--red); font-weight: 700; } .ap-act-reject { color: var(--red); font-weight: 700; }
.ap-gate { color: var(--muted); } .ap-gate { color: var(--muted); }
.ap-time { color: var(--faint); font-size: 11px; margin-left: auto; } .ap-time { color: var(--faint); font-size: 11px; margin-left: auto; }
.ap-reason { width: 100%; color: var(--amber); font-size: 12px; padding-left: 14px; } .ap-reason { width: 100%; color: var(--amber); font-size: 12px; display: flex; align-items: flex-start; gap: 5px; }
.ap-reason::before { content: '↳ '; color: var(--faint); } .ap-reason svg.icon { color: var(--faint); flex-shrink: 0; margin-top: 1px; }
.result-kv { font-size: 12px; display: grid; grid-template-columns: auto 1fr; gap: 3px 12px; } .result-kv { font-size: 12px; display: grid; grid-template-columns: auto 1fr; gap: 3px 12px; }
.result-kv dt { color: var(--muted); } .result-kv dt { color: var(--muted); }
@@ -657,7 +657,7 @@ button.cplx-btn:hover { box-shadow: 0 0 8px rgba(216,228,212,.18); }
border-radius: 1.5px; border-radius: 1.5px;
} }
.ev-head { display: flex; gap: 8px; align-items: baseline; } .ev-head { display: flex; gap: 8px; align-items: baseline; }
.ev-type { font-weight: 600; color: var(--ev, var(--ink)); letter-spacing: .04em; } .ev-type { font-weight: 600; color: var(--ev, var(--ink)); letter-spacing: .04em; display: inline-flex; align-items: center; gap: 4px; }
.ev-time { color: var(--faint); font-size: 10.5px; margin-left: auto; flex: none; } .ev-time { color: var(--faint); font-size: 10.5px; margin-left: auto; flex: none; }
.ev-detail { color: var(--muted); margin-top: 1px; word-break: break-word; } .ev-detail { color: var(--muted); margin-top: 1px; word-break: break-word; }
.ev-empty { padding: 30px 0; color: var(--faint); text-align: center; border: none !important; } .ev-empty { padding: 30px 0; color: var(--faint); text-align: center; border: none !important; }
@@ -827,6 +827,37 @@ li.ev-updated { --ev: var(--muted); }
.bdg-run:not(.zero) .bdg-ico { animation: pulse .9s infinite; } .bdg-run:not(.zero) .bdg-ico { animation: pulse .9s infinite; }
.bdg.zero { color: var(--faint); border-color: var(--line-soft); animation: none; } .bdg.zero { color: var(--faint); border-color: var(--line-soft); animation: none; }
/* ── SVG 图标集(v1.1 双色调) ── */
svg.icon {
display: inline-block;
width: 1em;
height: 1em;
vertical-align: -0.15em;
flex-shrink: 0;
overflow: visible;
}
.sec-head svg.icon,
.side-head svg.icon { width: 14px; height: 14px; vertical-align: -3px; }
/* 搜索输入框带图标包装 */
.filter-search-wrap {
position: relative; display: inline-flex; align-items: center;
}
.filter-search-wrap svg.icon {
position: absolute; left: 8px; width: 12px; height: 12px;
color: var(--muted); pointer-events: none; vertical-align: 0;
}
.filter-search-wrap input { padding-left: 28px; }
/* ── 图标语义色(--icon-accent 注入,暗/浅主题自动继承 CSS 变量) ── */
/* gate 紫 */
#bdgGate, .gate-section, .gate-card { --icon-accent: var(--violet); }
/* ready / task / add / sync / config / search / git 绿 */
#bdgReady, #topActions, .cfg-sync-row, .filter-search-wrap,
.dep-item.ok, #taskSection .sec-head, .side-head { --icon-accent: var(--green); }
/* running / event / flow 青 */
#bdgRun, .agent-section, #eventPanel { --icon-accent: var(--cyan); }
/* deps / reason 琥珀 */
#bdgBlocked, .dep-item.wait, .t-deps-wait, .ap-reason { --icon-accent: var(--amber); }
/* ── 依赖可视化 ── */ /* ── 依赖可视化 ── */
.t-deps-wait { .t-deps-wait {
flex: none; font-size: 10.5px; letter-spacing: .06em; flex: none; font-size: 10.5px; letter-spacing: .06em;