feat: 同步引擎+Agent配置+依赖自动落位+看板大改(预览/md渲染/筛选/徽章组)
后端: - src/sync/todo-sync.ts: todo.json 单向同步引擎(导入=首次同步,幂等,source_ref 映射, subs 复杂度修正为 easy,旧侧 done 历史事实优先 forceDone) - 依赖自动落位:ready 意图按 deps 落位 blocked,依赖全 done 自动放行, daemon 启动 reconcileDeps 对账,手动绕过会弹回 - 新 API: PATCH projects/:id(autonomy/concurrency)、POST :id/sync、GET /api/agents、 PATCH tasks/:id(title/priority/complexity 重置) - daemon 定时同步(MAESTRO_SYNC_INTERVAL 默认 300s) + project.synced 事件 - priority 语义翻转: P0 最高/P1 默认/P2 最低,取值限 0..2,排序/映射/MCP/CLI 全跟进 - 静态服务发 no-cache 头(修浏览器吃旧 CSS/JS) - schema 迁移: tasks.source_ref / projects.last_sync_at(ensureColumn 平滑升级旧库) 看板: - 全屏预览模式(94vh 读完整方案+就地裁决,Esc/遮罩/裁决自动关闭) - 产出 markdown 渲染为 HTML(零依赖渲染器,转义优先) - 任务树筛选(复杂度/状态分组/关键字)+ 顶栏徽章组(待审批/可执行/执行中,hover 展开) - 依赖可视化:详情 DEPS 区块 + 行内⛓等依赖 + 锚点跳转定位 - 按钮收敛:提交评审/编辑产出移除(CC 经 MCP 操作),界面只留用户动作 - Agent 执行面板 + 项目配置(并发/工作模式)+ 同步按钮 测试:21 个全过(新增 sync 幂等/迁移/patch/依赖落位/对账幂等) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+254
-2
@@ -113,6 +113,7 @@ body::before {
|
||||
#eventPanel .sec-head { padding: 18px 16px 10px; background: linear-gradient(var(--bg-deep) 75%, transparent); }
|
||||
.head-mark { color: var(--green); }
|
||||
.head-mark.amber { color: var(--amber); }
|
||||
.head-mark.cyan { color: var(--cyan); }
|
||||
.side-head .btn, .sec-head .btn { margin-left: auto; letter-spacing: .1em; }
|
||||
|
||||
/* ── 项目列表 ─────────────────────────────────────────────── */
|
||||
@@ -158,6 +159,66 @@ body::before {
|
||||
animation: pulse 2.2s infinite;
|
||||
}
|
||||
|
||||
/* 顶栏动作(同步 todo / 配置) */
|
||||
.top-actions { display: flex; align-items: center; gap: 8px; }
|
||||
.sync-meta { font-size: 10.5px; color: var(--faint); letter-spacing: .06em; }
|
||||
|
||||
/* ── 项目配置面板 ─────────────────────────────────────────── */
|
||||
.config-panel { padding: 12px 14px; margin-top: 12px; animation: rise .18s ease both; }
|
||||
.config-panel .form-row { align-items: flex-end; }
|
||||
.cfg-current {
|
||||
font-size: 11px; color: var(--muted); letter-spacing: .06em;
|
||||
margin-left: auto; padding-bottom: 7px;
|
||||
}
|
||||
|
||||
/* ── Agent 执行面板 ──────────────────────────────────────── */
|
||||
.agent-total { color: var(--cyan); letter-spacing: .08em; }
|
||||
.agent-empty {
|
||||
padding: 12px 14px; color: var(--faint); font-size: 12px;
|
||||
border: 1px dashed var(--line);
|
||||
}
|
||||
.agent-flex {
|
||||
display: flex; gap: 20px; align-items: stretch;
|
||||
background: var(--panel); border: 1px solid var(--cyan-dim);
|
||||
padding: 12px 16px;
|
||||
animation: rise .2s ease both;
|
||||
}
|
||||
.agent-big {
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
min-width: 64px; padding: 4px 8px;
|
||||
font-size: 40px; font-weight: 700; line-height: 1;
|
||||
color: var(--cyan); text-shadow: 0 0 18px rgba(89,200,216,.5);
|
||||
border-right: 1px solid var(--line-soft);
|
||||
}
|
||||
.agent-big-sub {
|
||||
font-size: 9px; font-weight: 600; letter-spacing: .3em;
|
||||
color: var(--muted); margin-top: 6px;
|
||||
}
|
||||
.agent-groups { flex: 1; display: grid; gap: 10px; min-width: 0; }
|
||||
.agent-proj-head {
|
||||
display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap;
|
||||
font-size: 12px; margin-bottom: 3px;
|
||||
}
|
||||
.agent-proj-head b { color: var(--ink); letter-spacing: .04em; }
|
||||
.agent-proj-meta { font-size: 10.5px; color: var(--faint); letter-spacing: .06em; }
|
||||
.agent-run {
|
||||
display: flex; gap: 8px; align-items: center;
|
||||
font-size: 12px; padding: 3px 0;
|
||||
border-bottom: 1px dashed var(--line-soft);
|
||||
}
|
||||
.agent-run:last-child { border-bottom: none; }
|
||||
.agent-run-dot {
|
||||
flex: none; width: 6px; height: 6px; border-radius: 50%;
|
||||
background: var(--cyan); box-shadow: 0 0 8px var(--cyan);
|
||||
animation: pulse .9s infinite;
|
||||
}
|
||||
.agent-run-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.agent-run-kind {
|
||||
flex: none; font-size: 10px; letter-spacing: .1em;
|
||||
color: var(--cyan); border: 1px solid var(--cyan-dim); padding: 0 6px;
|
||||
}
|
||||
.agent-run-time { flex: none; margin-left: auto; font-size: 10.5px; color: var(--faint); }
|
||||
|
||||
/* ── 审批闸 ─────────────────────────────────────────────── */
|
||||
.gate-section { margin-top: 18px; }
|
||||
.gate-stripe {
|
||||
@@ -279,6 +340,36 @@ textarea { resize: vertical; min-height: 72px; width: 100%; }
|
||||
.seg input:checked + label.seg-m { background: var(--amber-dim); color: var(--amber); }
|
||||
.seg input:checked + label.seg-e { background: var(--green-dim); color: var(--green); }
|
||||
|
||||
/* ── 任务树筛选栏 ─────────────────────────────────────────── */
|
||||
.filter-bar {
|
||||
background: var(--panel); border: 1px solid var(--line);
|
||||
padding: 8px 10px; margin-bottom: 12px;
|
||||
display: flex; flex-direction: column; gap: 7px;
|
||||
}
|
||||
.filter-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
|
||||
#filterKw { width: 190px; padding: 4px 9px; font-size: 12px; }
|
||||
.fchips { display: inline-flex; gap: 6px; }
|
||||
.fchip {
|
||||
font-family: var(--mono); font-size: 10px; font-weight: 700; letter-spacing: .1em;
|
||||
background: transparent; color: var(--faint);
|
||||
border: 1px solid var(--line); padding: 2px 8px;
|
||||
cursor: pointer; transition: all .1s;
|
||||
}
|
||||
.fchip:hover { color: var(--ink); border-color: var(--muted); }
|
||||
.fchip.on { color: var(--green); border-color: var(--green-dim); background: rgba(95,221,125,.08); }
|
||||
.fc-hard.on { color: var(--red); border-color: var(--red-dim); background: rgba(255,93,93,.08); }
|
||||
.fc-medium.on { color: var(--amber); border-color: var(--amber-dim); background: rgba(240,180,41,.08); }
|
||||
.fgroup {
|
||||
display: inline-flex; align-items: center; gap: 4px;
|
||||
padding: 2px 4px; border: 1px dashed var(--line-soft);
|
||||
}
|
||||
.fgroup-chip { color: var(--muted); }
|
||||
.fgroup-chip.part { color: var(--amber); border-color: var(--amber-dim); }
|
||||
.fst { font-weight: 400; letter-spacing: .04em; font-size: 10.5px; }
|
||||
.filter-count { font-size: 11px; color: var(--amber); letter-spacing: .08em; }
|
||||
.task-row.filter-ctx { opacity: .68; }
|
||||
.task-row.filter-ctx .t-title { color: var(--muted); }
|
||||
|
||||
/* ── 任务树 ─────────────────────────────────────────────── */
|
||||
.tree-empty {
|
||||
padding: 46px 0; text-align: center; color: var(--faint);
|
||||
@@ -324,6 +415,22 @@ textarea { resize: vertical; min-height: 72px; width: 100%; }
|
||||
.cplx-medium { color: var(--amber); border-color: var(--amber-dim); background: rgba(240,180,41,.07); }
|
||||
.cplx-easy { color: var(--green); border-color: var(--green-dim); background: rgba(95,221,125,.07); }
|
||||
|
||||
/* 复杂度徽章可点 + 下拉 */
|
||||
.cplx-wrap { position: relative; display: inline-flex; flex: none; }
|
||||
button.cplx { font-family: var(--mono); cursor: default; }
|
||||
button.cplx-btn { cursor: pointer; transition: box-shadow .12s; }
|
||||
button.cplx-btn:hover { box-shadow: 0 0 8px rgba(216,228,212,.18); }
|
||||
.cplx-pop {
|
||||
position: absolute; top: calc(100% + 5px); right: 0; z-index: 60;
|
||||
display: flex; gap: 5px;
|
||||
background: var(--bg-deep); border: 1px solid var(--line);
|
||||
padding: 6px; box-shadow: 0 10px 30px rgba(0,0,0,.65);
|
||||
animation: rise .12s ease both;
|
||||
}
|
||||
.cplx-pop .cplx { cursor: pointer; }
|
||||
.cplx-pop .cplx:hover { filter: brightness(1.35); }
|
||||
.cplx-pop .cplx.cur { outline: 1px solid currentColor; outline-offset: 1px; }
|
||||
|
||||
/* 状态 chip */
|
||||
.chip {
|
||||
flex: none; display: inline-flex; align-items: center; gap: 5px;
|
||||
@@ -360,6 +467,7 @@ textarea { resize: vertical; min-height: 72px; width: 100%; }
|
||||
}
|
||||
.detail-grid { display: grid; gap: 14px; }
|
||||
.detail-label { font-size: 10.5px; color: var(--muted); letter-spacing: .18em; margin-bottom: 4px; }
|
||||
.doc-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
||||
|
||||
.approval-item {
|
||||
display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap;
|
||||
@@ -437,7 +545,151 @@ li.ev-updated { --ev: var(--muted); }
|
||||
animation: toastIn .18s ease both;
|
||||
box-shadow: 0 8px 30px rgba(0,0,0,.55);
|
||||
}
|
||||
.toast.err { border-color: var(--red); color: var(--red); }
|
||||
.toast.ok { border-color: var(--green-dim); color: var(--green); }
|
||||
.toast.err { border-color: var(--red); color: var(--red); }
|
||||
.toast.ok { border-color: var(--green-dim); color: var(--green); }
|
||||
.toast.warn { border-color: var(--amber-dim); color: var(--amber); }
|
||||
.toast.out { opacity: 0; transition: opacity .3s; }
|
||||
@keyframes toastIn { from { opacity: 0; transform: translateY(8px); } }
|
||||
|
||||
/* 待 CC 产出占位 */
|
||||
.doc-pending { padding: 10px 12px; border: 1px dashed var(--line, #2a3a2a); color: var(--dim, #6a7a6a); font-style: italic; }
|
||||
|
||||
/* ── 全局预览模式(全屏读方案 + 就地裁决) ── */
|
||||
.preview-root { position: fixed; inset: 0; z-index: 1100; display: grid; place-items: center; }
|
||||
.preview-root[hidden] { display: none; }
|
||||
.preview-mask { position: absolute; inset: 0; background: rgba(4,6,5,.86); backdrop-filter: blur(3px); }
|
||||
.preview-panel {
|
||||
position: relative; display: flex; flex-direction: column;
|
||||
width: min(1080px, 94vw); height: 94vh;
|
||||
background: var(--panel); border: 1px solid var(--amber-dim);
|
||||
box-shadow: 0 0 0 1px var(--line-soft), 0 24px 64px rgba(0,0,0,.6);
|
||||
}
|
||||
.preview-head {
|
||||
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
|
||||
padding: 12px 16px; border-bottom: 1px solid var(--line);
|
||||
background: var(--panel-2);
|
||||
}
|
||||
.preview-title { font-weight: 700; font-size: 15px; color: var(--ink); }
|
||||
.preview-body {
|
||||
flex: 1; overflow-y: auto; padding: 16px 20px 28px;
|
||||
}
|
||||
.preview-body .doc {
|
||||
font-size: 13.5px; line-height: 1.7;
|
||||
white-space: pre-wrap; word-break: break-word;
|
||||
max-height: none; overflow-y: visible; /* 解除闸卡片 pre.doc 的 280px 限高,由 .preview-body 统一滚动 */
|
||||
}
|
||||
.preview-body .gate-doc-label { margin-top: 14px; }
|
||||
.preview-foot {
|
||||
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
|
||||
padding: 12px 16px; border-top: 1px solid var(--line);
|
||||
background: var(--panel-2);
|
||||
}
|
||||
.preview-reject { flex-basis: 100%; }
|
||||
.gate-preview-btn { margin-left: auto; }
|
||||
|
||||
/* ── Markdown 渲染产出(.doc.md:闸卡片 / 预览 / 任务详情统一) ── */
|
||||
.doc.md {
|
||||
background: var(--bg-deep);
|
||||
border: 1px solid var(--line-soft);
|
||||
border-left: 2px solid var(--green-dim);
|
||||
padding: 12px 16px;
|
||||
font-family: var(--mono); font-size: 12.5px; line-height: 1.7;
|
||||
color: var(--ink);
|
||||
white-space: normal; word-break: break-word;
|
||||
max-height: 280px; overflow-y: auto;
|
||||
}
|
||||
.gate-body .doc.md { max-height: 280px; }
|
||||
.preview-body .doc.md { max-height: none; overflow-y: visible; font-size: 13.5px; }
|
||||
.task-detail .doc.md { max-height: 360px; }
|
||||
|
||||
.doc.md .md-h { font-weight: 700; margin: 14px 0 6px; color: var(--green); }
|
||||
.doc.md .md-h:first-child { margin-top: 0; }
|
||||
.doc.md .md-h1 { font-size: 1.25em; border-bottom: 1px solid var(--line); padding-bottom: 4px; }
|
||||
.doc.md .md-h2 { font-size: 1.15em; color: var(--amber); }
|
||||
.doc.md .md-h3 { font-size: 1.05em; }
|
||||
.doc.md .md-h4, .doc.md .md-h5, .doc.md .md-h6 { font-size: 1em; color: var(--muted); }
|
||||
.doc.md p { margin: 6px 0; }
|
||||
.doc.md ul, .doc.md ol { margin: 6px 0; padding-left: 22px; }
|
||||
.doc.md li { margin: 3px 0; }
|
||||
.doc.md code {
|
||||
background: var(--panel-2); border: 1px solid var(--line-soft);
|
||||
padding: 0 4px; color: var(--cyan); font-size: .95em;
|
||||
}
|
||||
.doc.md pre.codeblock {
|
||||
background: var(--panel-2); border: 1px solid var(--line-soft);
|
||||
border-left: 2px solid var(--cyan-dim);
|
||||
padding: 10px 12px; margin: 8px 0;
|
||||
white-space: pre-wrap; word-break: break-word; overflow-x: auto;
|
||||
}
|
||||
.doc.md pre.codeblock code { background: none; border: none; padding: 0; }
|
||||
.doc.md blockquote {
|
||||
margin: 8px 0; padding: 6px 12px;
|
||||
border-left: 2px solid var(--amber-dim); color: var(--muted);
|
||||
background: var(--panel-2);
|
||||
}
|
||||
.doc.md hr { border: none; border-top: 1px dashed var(--line); margin: 12px 0; }
|
||||
.doc.md table { border-collapse: collapse; margin: 8px 0; width: 100%; }
|
||||
.doc.md th, .doc.md td { border: 1px solid var(--line); padding: 5px 10px; text-align: left; }
|
||||
.doc.md th { background: var(--panel-2); color: var(--green); }
|
||||
.doc.md a { color: var(--cyan); }
|
||||
|
||||
/* 顶栏:可执行数量徽章(与 gate-count 同形制,绿色系) */
|
||||
.ready-count {
|
||||
font-size: 11px; letter-spacing: .12em; color: var(--green);
|
||||
border: 1px solid var(--green-dim); padding: 4px 10px;
|
||||
}
|
||||
|
||||
/* ── 顶栏徽章组:默认 图标+数量,hover 展开文字 ── */
|
||||
.badge-group { display: flex; align-items: center; gap: 8px; }
|
||||
.bdg {
|
||||
display: inline-flex; align-items: center;
|
||||
font-size: 11.5px; line-height: 1; letter-spacing: .08em;
|
||||
height: 28px; padding: 0 10px; /* 固定高度对齐旁边的 .btn,防字形差异撑高 */
|
||||
cursor: default; white-space: nowrap;
|
||||
}
|
||||
.bdg .bdg-ico {
|
||||
margin-right: 5px; font-size: 12px; line-height: 1;
|
||||
font-family: var(--mono); /* 锁等宽字体,避免符号掉进 emoji 字体放大 */
|
||||
}
|
||||
.bdg .bdg-n { font-weight: 700; }
|
||||
.bdg .bdg-label {
|
||||
max-width: 0; opacity: 0; overflow: hidden;
|
||||
transition: max-width .28s ease, opacity .22s ease, margin-left .28s ease;
|
||||
}
|
||||
.bdg:hover .bdg-label { max-width: 8em; opacity: 1; margin-left: 5px; }
|
||||
|
||||
.bdg-gate { color: var(--amber); border: 1px solid var(--amber-dim); }
|
||||
.bdg-gate:not(.zero) { animation: pulse 2.2s infinite; }
|
||||
.bdg-ready { color: var(--green); border: 1px solid var(--green-dim); }
|
||||
.bdg-run { color: var(--cyan); border: 1px solid var(--cyan-dim); }
|
||||
.bdg-run:not(.zero) .bdg-ico { animation: pulse .9s infinite; }
|
||||
.bdg.zero { color: var(--faint); border-color: var(--line-soft); animation: none; }
|
||||
|
||||
/* ── 依赖可视化 ── */
|
||||
.t-deps-wait {
|
||||
flex: none; font-size: 10.5px; letter-spacing: .06em;
|
||||
color: var(--amber); border: 1px dashed var(--amber-dim);
|
||||
padding: 1px 7px; line-height: 1.5; white-space: nowrap;
|
||||
}
|
||||
.dep-list { display: grid; gap: 4px; }
|
||||
.dep-item {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
font-size: 12px; padding: 4px 10px;
|
||||
background: var(--bg-deep); border: 1px solid var(--line-soft);
|
||||
border-left: 2px solid var(--amber-dim);
|
||||
}
|
||||
.dep-item.ok { border-left-color: var(--green-dim); }
|
||||
.dep-item.ok .dep-mark { color: var(--green); }
|
||||
.dep-item.wait .dep-mark { color: var(--amber); }
|
||||
.dep-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
|
||||
/* 依赖锚点跳转 */
|
||||
.dep-item { cursor: pointer; transition: border-color .12s, background .12s; }
|
||||
.dep-item:hover { background: var(--panel-2); border-color: var(--muted); }
|
||||
.dep-go { margin-left: auto; color: var(--faint); font-size: 12px; }
|
||||
.dep-item:hover .dep-go { color: var(--cyan); }
|
||||
.task-row.flash { animation: locate-flash 1.8s ease-out; }
|
||||
@keyframes locate-flash {
|
||||
0%, 35% { background: var(--amber-dim); box-shadow: inset 2px 0 0 var(--amber); }
|
||||
100% { background: transparent; box-shadow: none; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user