@y9931666/dsh-plugin-roundtable 1.0.0-rc.2 → 1.0.0-rc.4

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 CHANGED
@@ -89,6 +89,15 @@
89
89
  | **崩溃不再无声无息** | DSH 会静默摘掉渲染抛错的 slot 条目(隔离正确,但用户只看到"页签不见了")。两个界面入口现在都包在错误边界里:崩溃渲染成可读报错面板 + 控制台完整堆栈,可直接贴 issue。**客户端改动刷新页面即生效。** |
90
90
  | **防回归闸门** | `test/inject.test.mjs` 直接对**构建产物**断言上述两条不变量(含"没有 connection 服务时仍必须注册两个入口"),旧产物必然失败。 |
91
91
 
92
+ ### 视觉与布局(v1.0.0-rc.4)
93
+ | 改动 | 说明 |
94
+ | --- | --- |
95
+ | **设置页轻分组** | 刻意**不套卡片**:用「细线 + 组标题」分组,避免与针锋的卡片语言冲突。控件统一 32px 高 / 8px 圆角 / 320 宽,面板开关改为等宽网格。 |
96
+ | **拓扑节点名可读** | 显示时去掉专家共有的 `expert-` 前缀并允许两行(`expert-lifecycle` → `lifecycle`),完整 key 仍由悬停 `title` 提供。 |
97
+ | **连线可读性** | 专家间连线从最低对比边框色改为次级文字色,线宽 1.6、圆端点。 |
98
+ | **右栏面板** | 面板标题升为正文色 + 字距,内容内边距加大,面板间距 12px。 |
99
+ | **区块图标** | 设置页各分组标题前加 16×16 内联 SVG 图标(零依赖、随主题变色)。 |
100
+
92
101
  ### 界面操作
93
102
  | 能力 | 说明 |
94
103
  | --- | --- |
package/lib/client.js CHANGED
@@ -100,7 +100,7 @@ const BRAND_LOGOS = {
100
100
 
101
101
  //#endregion
102
102
  //#region \0dsh-css:F:\AI\DSH\1changyong\dsh-plugin-roundtable\src\client\RoundTableView.module.css.mjs
103
- const css$1 = ".dd3px29_root {\n display: flex;\n height: 100%;\n min-height: 0;\n gap: 12px;\n padding: 12px;\n overflow: hidden;\n}\n\n/* ---------- main column ---------- */\n\n.dd3px29_main {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.dd3px29_header {\n display: flex;\n flex-direction: column;\n gap: 6px;\n flex: none;\n}\n\n.dd3px29_titleRow {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.dd3px29_meetingName {\n font-size: 15px;\n font-weight: 600;\n}\n\n.dd3px29_meetingSelect {\n font-size: 12px;\n padding: 2px 6px;\n border-radius: 6px;\n border: 1px solid rgba(128, 128, 128, 0.45);\n background: #ffffff;\n color: rgba(0, 0, 0, 0.8);\n max-width: 220px;\n}\n\n.dd3px29_badge {\n font-size: 11px;\n padding: 1px 8px;\n border-radius: 999px;\n border: 1px solid rgba(128, 128, 128, 0.35);\n color: rgba(0, 0, 0, 0.65);\n}\n\n.dd3px29_round {\n font-size: 12px;\n color: rgba(0, 0, 0, 0.55);\n}\n\n/* 标题区「针锋相对」入口:可点击打开评审窗口 */\n.dd3px29_reviewBadge {\n font-size: 11px;\n padding: 2px 10px;\n border-radius: 999px;\n border: 1px solid rgba(198, 40, 40, 0.45);\n background: rgba(198, 40, 40, 0.06);\n color: #c62828;\n cursor: pointer;\n font-weight: 600;\n transition: background 120ms ease;\n}\n\n.dd3px29_reviewBadge:hover {\n background: rgba(198, 40, 40, 0.14);\n}\n\n/* 右栏「分针记录」摘要卡片:可点击打开评审窗口 */\n.dd3px29_reviewCard {\n display: flex;\n flex-direction: column;\n gap: 4px;\n text-align: left;\n width: 100%;\n border: 1px solid rgba(198, 40, 40, 0.25);\n border-radius: 8px;\n background: rgba(198, 40, 40, 0.05);\n padding: 8px 10px;\n cursor: pointer;\n transition: background 120ms ease;\n}\n\n.dd3px29_reviewCard:hover {\n background: rgba(198, 40, 40, 0.1);\n}\n\n.dd3px29_reviewCardTitle {\n font-size: 12px;\n font-weight: 600;\n color: rgba(0, 0, 0, 0.8);\n line-height: 1.45;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.dd3px29_reviewCardMeta {\n font-size: 10px;\n color: rgba(0, 0, 0, 0.5);\n}\n\n/* 会议删除按钮:固定在右栏顶部操作条(不再挤在标题行最右,窄屏也能看到)。 */\n.dd3px29_meetingDelete {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 12px;\n padding: 4px 10px;\n border-radius: 6px;\n border: 1px solid rgba(128, 128, 128, 0.35);\n background: #ffffff;\n color: rgba(0, 0, 0, 0.7);\n cursor: pointer;\n opacity: 0.75;\n transition: opacity 120ms ease, border-color 120ms ease, color 120ms ease;\n}\n\n.dd3px29_meetingDelete:hover {\n opacity: 1;\n border-color: #c62828;\n color: #c62828;\n}\n\n.dd3px29_budgetRow {\n display: flex;\n align-items: center;\n gap: 6px;\n flex-wrap: wrap;\n}\n\n.dd3px29_budgetLabel {\n font-size: 11px;\n color: rgba(0, 0, 0, 0.55);\n}\n\n.dd3px29_budgetBar {\n width: 90px;\n height: 5px;\n border-radius: 999px;\n background: rgba(128, 128, 128, 0.18);\n overflow: hidden;\n}\n\n.dd3px29_budgetFill {\n height: 100%;\n border-radius: 999px;\n background: #4d6bfe;\n}\n\n.dd3px29_budgetFillTokens {\n height: 100%;\n border-radius: 999px;\n background: #10a37f;\n}\n\n.dd3px29_budgetValue {\n font-size: 11px;\n color: rgba(0, 0, 0, 0.65);\n font-variant-numeric: tabular-nums;\n}\n\n.dd3px29_decisionBanner {\n display: flex;\n align-items: baseline;\n gap: 8px;\n flex-wrap: wrap;\n padding: 6px 10px;\n border-radius: 8px;\n background: rgba(214, 158, 46, 0.12);\n border: 1px solid rgba(214, 158, 46, 0.4);\n}\n\n.dd3px29_decisionTag {\n font-size: 11px;\n font-weight: 600;\n color: #a06b00;\n flex: none;\n}\n\n.dd3px29_decisionQuestion {\n font-size: 12px;\n color: rgba(0, 0, 0, 0.75);\n}\n\n/* ---------- topology canvas ---------- */\n\n.dd3px29_canvas {\n position: relative;\n flex: 1;\n min-width: 320px;\n min-height: 420px;\n border-radius: 12px;\n background: #fafafa;\n border: 1px solid rgba(128, 128, 128, 0.15);\n overflow: hidden;\n}\n\n/* A1 图例:把「这张图在当前模式下该怎么读」直接写在画布上。\n 起因是红队评审的两条认定缺陷(gpt-1 / claude-2):同一张通道图在\n 主持人统筹 / 多模型平等 / 针锋相对三种模式下含义完全不同,不写清楚,\n 用户会把它读成一张\"控制流程的路径图\"。\n pointer-events:none —— 它只是说明文字,绝不能挡住节点的拖拽拉线。\n 位置放左上角:主持人节点锚在左侧垂直居中(x≈10% w, y=50% h),\n 右上是连线最稀疏的区域。 */\n.dd3px29_canvasLegend {\n position: absolute;\n top: 10px;\n left: 12px;\n z-index: 3;\n max-width: 300px;\n /* 右侧留出叉号的位置,避免文字压到关闭按钮下面。 */\n padding: 7px 24px 7px 10px;\n border-radius: 8px;\n background: rgba(255, 255, 255, 0.88);\n border: 1px solid rgba(128, 128, 128, 0.22);\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);\n pointer-events: none;\n line-height: 1.5;\n}\n\n/* 关闭按钮:父级是 pointer-events:none(图例绝不能挡住拖拽拉线),\n 所以这里必须单独把事件收回来。 */\n.dd3px29_canvasLegendClose {\n position: absolute;\n top: 4px;\n right: 4px;\n width: 16px;\n height: 16px;\n padding: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n line-height: 1;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: rgba(0, 0, 0, 0.4);\n cursor: pointer;\n pointer-events: auto;\n}\n\n.dd3px29_canvasLegendClose:hover {\n background: rgba(0, 0, 0, 0.08);\n color: rgba(0, 0, 0, 0.75);\n}\n\n.dd3px29_canvasLegendTitle {\n font-size: 11px;\n font-weight: 600;\n color: rgba(0, 0, 0, 0.62);\n margin-bottom: 2px;\n}\n\n.dd3px29_canvasLegendRow {\n font-size: 11px;\n color: rgba(0, 0, 0, 0.72);\n}\n\n.dd3px29_canvasLegendHint {\n font-size: 10.5px;\n color: rgba(0, 0, 0, 0.45);\n margin-top: 2px;\n}\n\n.dd3px29_edgeLayer {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n\n.dd3px29_edgeGroup {\n pointer-events: auto;\n cursor: pointer;\n}\n\n.dd3px29_edgeGroup path {\n pointer-events: stroke;\n}\n\n/* Hover delete affordance: a red circle with an X floating ABOVE the canvas\n at the edge's midpoint. It lives in a top layer (not the SVG, which sits\n under the nodes) so a midpoint that lands inside a node is never hidden.\n Revealed while the edge (or the button itself) is hovered. */\n.dd3px29_edgeRemove {\n position: absolute;\n transform: translate(-50%, -50%);\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: #c62828;\n border: 1.5px solid #ffffff;\n color: #ffffff;\n font-size: 12px;\n display: flex;\n align-items: center;\n justify-content: center;\n line-height: 1;\n padding: 0;\n cursor: pointer;\n z-index: 6;\n opacity: 0;\n pointer-events: none;\n transition: opacity 120ms ease;\n box-shadow: 0 1px 5px rgba(0, 0, 0, 0.28);\n}\n\n.dd3px29_edgeRemoveActive {\n opacity: 1;\n pointer-events: auto;\n}\n\n.dd3px29_edgeRemove:hover {\n opacity: 1;\n pointer-events: auto;\n background: #b71c1c;\n}\n\n.dd3px29_edgeForward {\n stroke: rgba(90, 90, 90, 0.5);\n stroke-width: 1.5;\n}\n\n.dd3px29_edgeBidirectional {\n stroke: rgba(77, 107, 254, 0.7);\n stroke-width: 1.6;\n}\n\n.dd3px29_edgeArrow {\n fill: rgba(90, 90, 90, 0.7);\n}\n\n.dd3px29_edgeSynthetic {\n stroke: rgba(90, 90, 90, 0.28);\n stroke-width: 1.3;\n stroke-dasharray: 4 4;\n}\n\n.dd3px29_edgeArrowSynthetic {\n fill: rgba(90, 90, 90, 0.38);\n}\n\n/* Role-based accents so the topology reads at a glance: captain is the single\n \"everything radiates from here\" hub (warm gold), aggregator is the sink\n (cool teal), and expert↔expert wires stay neutral grey. */\n.dd3px29_edgeCaptain {\n stroke: rgba(214, 158, 46, 0.85);\n stroke-width: 1.8;\n}\n\n.dd3px29_edgeAggregator {\n stroke: rgba(16, 163, 127, 0.8);\n stroke-width: 1.6;\n}\n\n.dd3px29_edgeSyntheticCaptain {\n stroke: rgba(214, 158, 46, 0.45);\n stroke-width: 1.4;\n stroke-dasharray: 4 4;\n}\n\n.dd3px29_edgeSyntheticAggregator {\n stroke: rgba(16, 163, 127, 0.4);\n stroke-width: 1.4;\n stroke-dasharray: 4 4;\n}\n\n.dd3px29_edgeArrowCaptain {\n fill: rgba(214, 158, 46, 0.9);\n}\n\n.dd3px29_edgeArrowAggregator {\n fill: rgba(16, 163, 127, 0.85);\n}\n\n.dd3px29_edgeArrowSyntheticCaptain {\n fill: rgba(214, 158, 46, 0.5);\n}\n\n.dd3px29_edgeArrowSyntheticAggregator {\n fill: rgba(16, 163, 127, 0.45);\n}\n\n.dd3px29_dragLine {\n stroke: #4d6bfe;\n stroke-width: 1.6;\n stroke-dasharray: 5 4;\n pointer-events: none;\n}\n\n/* One-shot message pulse traveling from sender to receiver. */\n.dd3px29_msgPulse {\n position: absolute;\n left: 0;\n top: 0;\n width: 9px;\n height: 9px;\n border-radius: 50%;\n background: #4d6bfe;\n box-shadow: 0 0 10px 2px rgba(77, 107, 254, 0.7);\n pointer-events: none;\n z-index: 4;\n animation: rt-msg-flow 1.1s ease-out forwards;\n}\n\n@keyframes rt-msg-flow {\n 0% {\n transform: translate(var(--rt-fx), var(--rt-fy)) translate(-50%, -50%);\n opacity: 1;\n }\n 100% {\n transform: translate(var(--rt-tx), var(--rt-ty)) translate(-50%, -50%);\n opacity: 0;\n }\n}\n\n.dd3px29_node {\n position: absolute;\n transform: translate(-50%, -50%);\n width: 68px;\n height: 68px;\n border-radius: 50%;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 2px;\n background: #ffffff;\n border: 1.5px solid rgba(120, 120, 120, 0.4);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n user-select: none;\n z-index: 2;\n}\n\n.dd3px29_captainNode {\n width: 96px;\n height: 88px;\n border-radius: 12px;\n background: #202124;\n border: none;\n box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);\n}\n\n.dd3px29_captainNode .dd3px29_nodeLabel {\n color: #e8eaed;\n font-size: 12px;\n font-weight: 600;\n text-align: center;\n line-height: 1.4;\n}\n\n.dd3px29_aggregatorNode {\n width: 74px;\n height: 74px;\n border-radius: 14px;\n background: #f3f4f6;\n border: 1.5px dashed rgba(120, 120, 120, 0.5);\n}\n\n.dd3px29_nodeLabel {\n font-size: 11px;\n color: rgba(0, 0, 0, 0.85);\n text-align: center;\n line-height: 1.3;\n max-width: 62px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Captain/aggregator labels are longer (\"DeepSeek · 主持\", \"汇聚网关\") and are\n clipped into \"DeepSee…\" by the 62px default. Give them room to wrap to two\n lines so the full name shows — the truncation was hiding the final \"K\" and\n broke the reading of the host's name. */\n.dd3px29_captainNode .dd3px29_nodeLabel,\n.dd3px29_aggregatorNode .dd3px29_nodeLabel {\n max-width: 86px;\n white-space: normal;\n overflow-wrap: break-word;\n word-break: break-word;\n}\n\n.dd3px29_avatar {\n width: 30px;\n height: 30px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n flex: none;\n box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.22);\n}\n\n/* 厂商 logo 图:圆形裁切 + contain 防切边,品牌色底垫在透明图后面。 */\n.dd3px29_avatarImg {\n width: 100%;\n height: 100%;\n border-radius: 50%;\n object-fit: contain;\n padding: 2px;\n box-sizing: border-box;\n}\n\n.dd3px29_avatarAbbr {\n color: #ffffff;\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.3px;\n line-height: 1;\n}\n\n/* A2:节点状态如实显示持久子代理的生命周期。默认中性灰(空闲/就绪/未唤醒\n 都不代表节点坏了),只有真的在跑才用绿色高亮。\n 节点只有 68px 宽,所以这里强制单行 + 省略号:状态标签永远不能换行,\n 否则文字会溢出圆形卡片、和连线叠在一起(\"就绪(可唤醒)\"就是这么翻车的)。\n 完整语义交给 title(activityLifecycleHint)与右栏列表的悬停提示。 */\n.dd3px29_nodeMeta {\n font-size: 10px;\n color: rgba(0, 0, 0, 0.45);\n min-height: 12px;\n max-width: calc(100% - 4px);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.dd3px29_nodeMetaWorking {\n color: #10a37f;\n}\n\n/* A node that was added then removed: keep it visible so the topology still\n shows which models participated, but degrade it (greyed, no pulse, muted). */\n.dd3px29_nodeRemoved {\n background: #f4f4f4;\n border-color: rgba(128, 128, 128, 0.35);\n box-shadow: none;\n opacity: 0.72;\n}\n\n.dd3px29_nodeRemoved .dd3px29_nodeLabel {\n color: rgba(0, 0, 0, 0.5);\n text-decoration: line-through;\n}\n\n.dd3px29_nodeRemoved .dd3px29_avatar {\n filter: grayscale(0.8);\n opacity: 0.85;\n}\n\n/* Breathing light: low-opacity pulse, pure CSS — zero tokens. */\n.dd3px29_breathing {\n animation: roundtable-pulse 1.6s ease-in-out infinite;\n}\n\n@keyframes roundtable-pulse {\n 0%, 100% {\n box-shadow: 0 0 0 0 rgba(77, 107, 254, 0.28), 0 2px 8px rgba(0, 0, 0, 0.08);\n border-color: rgba(77, 107, 254, 0.75);\n }\n 50% {\n box-shadow: 0 0 0 9px rgba(77, 107, 254, 0), 0 2px 8px rgba(0, 0, 0, 0.08);\n border-color: rgba(77, 107, 254, 0.45);\n }\n}\n\n/* Connector ports: small hit-dots on the node rim that a connector grows out\n of (a Dify-style workflow port). Four per node; each is a 10px circle that\n brightens on hover to signal \"drag from here to wire an edge\". The dots sit\n outside the node circle so the wire visibly starts at the expert's edge. */\n.dd3px29_port {\n position: absolute;\n width: 11px;\n height: 11px;\n border-radius: 50%;\n background: #ffffff;\n border: 2px solid #4d6bfe;\n box-shadow: 0 0 0 2px rgba(77, 107, 254, 0.15);\n cursor: crosshair;\n opacity: 0;\n transition: opacity 120ms ease, transform 120ms ease, box-shadow 120ms ease;\n z-index: 4;\n}\n\n.dd3px29_node:hover .dd3px29_port,\n.dd3px29_port:focus-visible {\n opacity: 1;\n}\n\n.dd3px29_port:hover {\n transform: scale(1.25);\n box-shadow: 0 0 0 4px rgba(77, 107, 254, 0.28);\n background: #4d6bfe;\n}\n\n/* Placement: centered on each rim, slightly outside the 68px circle. */\n.dd3px29_portN { top: -7px; left: calc(50% - 5.5px); }\n.dd3px29_portE { right: -7px; top: calc(50% - 5.5px); }\n.dd3px29_portS { bottom: -7px; left: calc(50% - 5.5px); }\n.dd3px29_portW { left: -7px; top: calc(50% - 5.5px); }\n\n/* Larger captain/aggregator rims need the ports pushed a touch further out. */\n.dd3px29_captainNode .dd3px29_portN { top: -9px; }\n.dd3px29_captainNode .dd3px29_portS { bottom: -9px; }\n.dd3px29_captainNode .dd3px29_portE { right: -9px; }\n.dd3px29_captainNode .dd3px29_portW { left: -9px; }\n.dd3px29_aggregatorNode .dd3px29_portN { top: -8px; }\n.dd3px29_aggregatorNode .dd3px29_portS { bottom: -8px; }\n.dd3px29_aggregatorNode .dd3px29_portE { right: -8px; }\n.dd3px29_aggregatorNode .dd3px29_portW { left: -8px; }\n\n/* While a connector is being dragged, keep every port visible so the user can\n see exactly where they may drop — ports otherwise only appear on hover. */\n.dd3px29_canvasDragging .dd3px29_port {\n opacity: 0.6;\n}\n\n/* ---------- gateway digest ---------- */\n\n.dd3px29_digest {\n flex: none;\n border: 1px solid rgba(128, 128, 128, 0.2);\n border-radius: 10px;\n background: #fdfdfd;\n font-size: 12px;\n}\n\n.dd3px29_digest summary {\n cursor: pointer;\n padding: 6px 10px;\n font-weight: 600;\n color: rgba(0, 0, 0, 0.7);\n}\n\n.dd3px29_digestBody {\n margin: 0;\n padding: 0 10px 10px;\n max-height: 180px;\n overflow: auto;\n font-family: inherit;\n white-space: pre-wrap;\n word-break: break-word;\n color: rgba(0, 0, 0, 0.7);\n}\n\n/* ---------- sidebar ---------- */\n\n.dd3px29_sidebar {\n width: 264px;\n flex: none;\n display: flex;\n flex-direction: column;\n gap: 10px;\n overflow-y: auto;\n padding-right: 2px;\n}\n\n/* 右栏顶部操作条:删除会议 + 待办徽标(固定位置,窄屏可见)。 */\n.dd3px29_sidebarActions {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n flex: none;\n}\n\n.dd3px29_pendingBadge {\n font-size: 11px;\n color: #b26a00;\n background: rgba(255, 193, 7, 0.14);\n border: 1px solid rgba(255, 193, 7, 0.5);\n border-radius: 999px;\n padding: 2px 8px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n min-width: 0;\n}\n\n/* 专家行\"待移除\"标记(用户已删除、主持人下一轮执行)。 */\n.dd3px29_agentQueuedRemove {\n font-size: 10px;\n color: #b26a00;\n flex: none;\n font-weight: 600;\n}\n\n.dd3px29_panel {\n flex: none;\n border: 1px solid rgba(128, 128, 128, 0.18);\n border-radius: 10px;\n background: #fdfdfd;\n overflow: hidden;\n}\n\n.dd3px29_panelTitleRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 6px 10px;\n border-bottom: 1px solid rgba(128, 128, 128, 0.12);\n background: #fafafa;\n}\n\n.dd3px29_panelTitle {\n font-size: 12px;\n font-weight: 600;\n color: rgba(0, 0, 0, 0.7);\n}\n\n.dd3px29_panelAdd {\n width: 18px;\n height: 18px;\n border-radius: 50%;\n border: 1px solid rgba(128, 128, 128, 0.4);\n background: #ffffff;\n color: rgba(0, 0, 0, 0.6);\n font-size: 12px;\n line-height: 16px;\n text-align: center;\n padding: 0;\n cursor: pointer;\n flex: none;\n}\n\n.dd3px29_panelAdd:hover {\n border-color: #4d6bfe;\n color: #4d6bfe;\n}\n\n.dd3px29_panelBody {\n padding: 8px 10px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n max-height: 220px;\n overflow-y: auto;\n}\n\n.dd3px29_agentRow {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.dd3px29_agentAvatar {\n width: 26px;\n height: 26px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n flex: none;\n}\n\n.dd3px29_agentAbbr {\n color: #ffffff;\n font-size: 9px;\n font-weight: 700;\n}\n\n.dd3px29_agentInfo {\n flex: 1;\n min-width: 0;\n}\n\n.dd3px29_agentName {\n font-size: 12px;\n font-weight: 600;\n color: rgba(0, 0, 0, 0.85);\n}\n\n.dd3px29_agentRole {\n font-size: 10px;\n color: rgba(0, 0, 0, 0.5);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.dd3px29_agentStatus {\n font-size: 10px;\n color: rgba(0, 0, 0, 0.45);\n flex: none;\n}\n\n.dd3px29_agentStatusWorking {\n font-size: 10px;\n color: #10a37f;\n flex: none;\n font-weight: 600;\n}\n\n.dd3px29_agentRowRemoved {\n opacity: 0.6;\n}\n\n.dd3px29_agentRowRemoved .dd3px29_avatar,\n.dd3px29_agentRowRemoved .dd3px29_agentAvatar {\n filter: grayscale(0.8);\n}\n\n.dd3px29_agentRowRemoved .dd3px29_agentName {\n color: rgba(0, 0, 0, 0.5);\n text-decoration: line-through;\n}\n\n.dd3px29_agentStatusRemoved {\n font-size: 10px;\n color: rgba(0, 0, 0, 0.4);\n font-style: italic;\n flex: none;\n}\n\n.dd3px29_taskRow {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.dd3px29_taskToggle {\n display: flex;\n align-items: center;\n gap: 6px;\n background: none;\n border: none;\n padding: 0;\n cursor: pointer;\n text-align: left;\n border-radius: 4px;\n}\n\n.dd3px29_taskToggle:hover {\n background: rgba(77, 107, 254, 0.08);\n}\n\n.dd3px29_taskChevron {\n font-size: 11px;\n color: rgba(0, 0, 0, 0.55);\n width: 12px;\n flex: none;\n}\n\n.dd3px29_taskKey {\n font-size: 11px;\n font-weight: 600;\n color: rgba(0, 0, 0, 0.75);\n}\n\n/* Collapsed: always a single line with an ellipsis, regardless of length. */\n.dd3px29_taskRole {\n font-size: 11px;\n color: rgba(0, 0, 0, 0.55);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n line-height: 1.35;\n}\n\n/* Expanded: full role, wrapped. */\n.dd3px29_taskRoleExpanded {\n font-size: 11px;\n color: rgba(0, 0, 0, 0.6);\n line-height: 1.45;\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.dd3px29_panelEmpty {\n font-size: 11px;\n color: rgba(0, 0, 0, 0.45);\n line-height: 1.5;\n}\n\n.dd3px29_logRow {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding-bottom: 6px;\n border-bottom: 1px dashed rgba(128, 128, 128, 0.15);\n}\n\n.dd3px29_logRow:last-child {\n border-bottom: none;\n padding-bottom: 0;\n}\n\n.dd3px29_logHead {\n display: flex;\n justify-content: space-between;\n gap: 6px;\n}\n\n.dd3px29_logFrom {\n font-size: 10px;\n font-weight: 600;\n color: rgba(77, 107, 254, 0.9);\n}\n\n.dd3px29_logTime {\n font-size: 10px;\n color: rgba(0, 0, 0, 0.4);\n font-variant-numeric: tabular-nums;\n}\n\n.dd3px29_logText {\n font-size: 11px;\n color: rgba(0, 0, 0, 0.65);\n line-height: 1.45;\n}\n\n/* ---------- context menu ---------- */\n\n.dd3px29_contextMenu {\n position: fixed;\n z-index: 1000;\n min-width: 170px;\n background: #ffffff;\n border: 1px solid rgba(128, 128, 128, 0.3);\n border-radius: 8px;\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.14);\n padding: 4px;\n display: flex;\n flex-direction: column;\n}\n\n.dd3px29_contextMenuTitle {\n font-size: 11px;\n color: rgba(0, 0, 0, 0.5);\n padding: 4px 10px;\n border-bottom: 1px solid rgba(128, 128, 128, 0.15);\n margin-bottom: 4px;\n}\n\n.dd3px29_contextMenuItem,\n.dd3px29_contextMenuItemDanger {\n border: none;\n background: none;\n text-align: left;\n font-size: 13px;\n padding: 7px 10px;\n border-radius: 6px;\n cursor: pointer;\n color: rgba(0, 0, 0, 0.85);\n}\n\n.dd3px29_contextMenuItem:hover {\n background: rgba(77, 107, 254, 0.1);\n}\n\n.dd3px29_contextMenuItemDanger {\n color: #c62828;\n}\n\n.dd3px29_contextMenuItemDanger:hover {\n background: rgba(198, 40, 40, 0.08);\n}\n\n/* ---------- empty / error ---------- */\n\n.dd3px29_emptyState {\n height: 100%;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 10px;\n padding: 24px;\n text-align: center;\n}\n\n.dd3px29_emptyTitle {\n font-size: 15px;\n font-weight: 600;\n color: rgba(0, 0, 0, 0.75);\n}\n\n.dd3px29_emptyHint {\n font-size: 13px;\n color: rgba(0, 0, 0, 0.5);\n max-width: 420px;\n line-height: 1.6;\n}\n\n.dd3px29_fetchFailed {\n font-size: 11px;\n color: #c62828;\n}\n\n/* Connect-result toast: a short-lived banner at the top of the topology\n canvas telling the user whether a dragged wire landed. Without it a failed\n edge.dd3px29_add (e.dd3px29_g. wiring a removed node) was silently swallowed — the user saw\n nothing and assumed the drag did nothing. White card, black text, no tilt:\n the label sits on a single centered baseline. */\n.dd3px29_toast {\n position: absolute;\n top: 12px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 6;\n max-width: 90%;\n background: #ffffff;\n border: 1px solid rgba(128, 128, 128, 0.35);\n border-radius: 8px;\n padding: 8px 16px;\n box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);\n pointer-events: none;\n display: flex;\n align-items: center;\n justify-content: center;\n line-height: 1.4;\n text-align: center;\n font-variant-numeric: tabular-nums;\n}\n\n.dd3px29_toastOk {\n font-size: 12.5px;\n color: #1a1a1a;\n font-weight: 500;\n}\n\n.dd3px29_toastErr {\n font-size: 12.5px;\n color: #b3261e;\n font-weight: 500;\n}\n\n/* ---------- expert management modal (panel \"+\" buttons) ---------- */\n\n.dd3px29_manageOverlay {\n position: fixed;\n inset: 0;\n z-index: 1200;\n background: rgba(0, 0, 0, 0.35);\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.dd3px29_manageModal {\n width: 480px;\n max-width: calc(100vw - 32px);\n max-height: calc(100vh - 48px);\n overflow-y: auto;\n background: #ffffff;\n border-radius: 12px;\n box-shadow: 0 12px 40px rgba(0, 0, 0, 0.22);\n padding: 16px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.dd3px29_manageTitle {\n font-size: 14px;\n font-weight: 600;\n color: rgba(0, 0, 0, 0.85);\n}\n\n.dd3px29_managePendingNote {\n font-size: 11px;\n color: #7a5b00;\n background: rgba(255, 193, 7, 0.12);\n border: 1px solid rgba(255, 193, 7, 0.45);\n border-radius: 8px;\n padding: 6px 10px;\n line-height: 1.5;\n}\n\n.dd3px29_manageSectionTitle {\n font-size: 12px;\n font-weight: 600;\n color: rgba(0, 0, 0, 0.7);\n margin-top: 2px;\n}\n\n.dd3px29_manageList {\n display: flex;\n flex-direction: column;\n gap: 6px;\n max-height: 220px;\n overflow-y: auto;\n border: 1px solid rgba(128, 128, 128, 0.15);\n border-radius: 8px;\n padding: 8px;\n background: #fafafa;\n}\n\n.dd3px29_manageRow {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.dd3px29_managePendingAvatar {\n width: 26px;\n height: 26px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n flex: none;\n background: rgba(255, 193, 7, 0.25);\n color: #b26a00;\n font-size: 14px;\n font-weight: 700;\n}\n\n.dd3px29_manageStatusTag {\n font-size: 10px;\n color: rgba(0, 0, 0, 0.45);\n flex: none;\n}\n\n.dd3px29_managePendingTag {\n font-size: 10px;\n color: #b26a00;\n background: rgba(255, 193, 7, 0.15);\n border-radius: 999px;\n padding: 2px 8px;\n flex: none;\n font-weight: 600;\n}\n\n.dd3px29_manageRemoveBtn {\n font-size: 11px;\n padding: 2px 10px;\n border-radius: 6px;\n border: 1px solid rgba(198, 40, 40, 0.4);\n background: #ffffff;\n color: #c62828;\n cursor: pointer;\n flex: none;\n}\n\n.dd3px29_manageRemoveBtn:hover {\n background: rgba(198, 40, 40, 0.08);\n}\n\n.dd3px29_manageForm {\n display: flex;\n flex-direction: column;\n gap: 8px;\n border: 1px solid rgba(128, 128, 128, 0.15);\n border-radius: 8px;\n padding: 10px;\n background: #fafafa;\n}\n\n.dd3px29_manageFormRow {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.dd3px29_manageLabel {\n font-size: 12px;\n color: rgba(0, 0, 0, 0.65);\n width: 40px;\n flex: none;\n}\n\n.dd3px29_manageInput {\n flex: 1;\n min-width: 0;\n font-size: 12px;\n padding: 4px 8px;\n border-radius: 6px;\n border: 1px solid rgba(128, 128, 128, 0.4);\n background: #ffffff;\n color: rgba(0, 0, 0, 0.85);\n}\n\n.dd3px29_manageSelect {\n flex: 1;\n min-width: 0;\n font-size: 12px;\n padding: 3px 6px;\n border-radius: 6px;\n border: 1px solid rgba(128, 128, 128, 0.4);\n background: #ffffff;\n color: rgba(0, 0, 0, 0.85);\n}\n\n.dd3px29_manageAddBtn {\n align-self: flex-end;\n font-size: 12px;\n padding: 4px 14px;\n border-radius: 6px;\n border: none;\n background: #4d6bfe;\n color: #ffffff;\n cursor: pointer;\n}\n\n.dd3px29_manageAddBtn:hover {\n background: #3b58e8;\n}\n\n.dd3px29_manageFooter {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n border-top: 1px solid rgba(128, 128, 128, 0.15);\n padding-top: 10px;\n}\n\n.dd3px29_manageHint {\n font-size: 11px;\n color: rgba(0, 0, 0, 0.5);\n line-height: 1.5;\n}\n\n.dd3px29_manageCloseBtn {\n font-size: 12px;\n padding: 4px 14px;\n border-radius: 6px;\n border: 1px solid rgba(128, 128, 128, 0.35);\n background: #ffffff;\n color: rgba(0, 0, 0, 0.7);\n cursor: pointer;\n flex: none;\n}\n\n.dd3px29_manageCloseBtn:hover {\n border-color: #4d6bfe;\n color: #4d6bfe;\n}\n\n/* ---------- 右栏面板可见性(R3:设置页点圆圈决定显示) ---------- */\n\n/* 被用户关掉的面板不渲染(右栏因此变短,拓扑区自然变大)。 */\n.dd3px29_panelHidden {\n display: none !important;\n}\n\n/* ---------- knowledge base (阅览版) ---------- */\n\n/* skill 传递方式的简短标注(与面板标题同一行,保持界面工整)。 */\n.dd3px29_skillDeliveryLabel {\n font-size: 11px;\n color: rgba(0, 0, 0, 0.45);\n font-weight: 400;\n}\n\n.dd3px29_kbPathRow {\n font-size: 11px;\n color: rgba(0, 0, 0, 0.65);\n background: rgba(77, 107, 254, 0.07);\n border: 1px solid rgba(77, 107, 254, 0.25);\n border-radius: 6px;\n padding: 5px 8px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n word-break: break-all;\n}\n\n.dd3px29_kbPathForm {\n display: flex;\n flex-direction: column;\n gap: 8px;\n border: 1px solid rgba(128, 128, 128, 0.15);\n border-radius: 8px;\n padding: 10px;\n background: #fafafa;\n}\n\n.dd3px29_kbChangedRow {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n color: rgba(0, 0, 0, 0.7);\n cursor: pointer;\n user-select: none;\n}\n\n.dd3px29_switchInput {\n accent-color: #4d6bfe;\n cursor: pointer;\n}\n\n.dd3px29_kbList {\n display: flex;\n flex-direction: column;\n gap: 4px;\n max-height: 240px;\n overflow-y: auto;\n border: 1px solid rgba(128, 128, 128, 0.15);\n border-radius: 8px;\n padding: 8px;\n background: #fafafa;\n}\n\n.dd3px29_kbRow {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n}\n\n.dd3px29_kbKindDir,\n.dd3px29_kbKindFile {\n font-size: 13px;\n flex: none;\n opacity: 0.85;\n}\n\n.dd3px29_kbName {\n flex: 1;\n min-width: 0;\n color: rgba(0, 0, 0, 0.85);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.dd3px29_kbMeta {\n font-size: 10px;\n color: rgba(0, 0, 0, 0.45);\n flex: none;\n}\n\n.dd3px29_kbError {\n font-size: 12px;\n color: #c62828;\n line-height: 1.5;\n}\n\n/* ---------- 针锋相对评审弹窗(三区布局) ---------- */\n\n.dd3px29_reviewModal {\n width: 860px;\n max-width: calc(100vw - 32px);\n max-height: calc(100vh - 48px);\n overflow-y: auto;\n background: #ffffff;\n border-radius: 12px;\n box-shadow: 0 12px 40px rgba(0, 0, 0, 0.22);\n padding: 16px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.dd3px29_reviewBody {\n display: flex;\n gap: 12px;\n min-height: 0;\n}\n\n.dd3px29_reviewLeft {\n flex: 0 0 300px;\n display: flex;\n flex-direction: column;\n gap: 6px;\n border-right: 1px solid rgba(128, 128, 128, 0.15);\n padding-right: 12px;\n max-height: 60vh;\n overflow-y: auto;\n}\n\n.dd3px29_reviewRight {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.dd3px29_reviewSectionTitle {\n font-size: 12px;\n font-weight: 600;\n color: rgba(0, 0, 0, 0.7);\n}\n\n.dd3px29_reviewQuestion {\n font-size: 13px;\n font-weight: 600;\n color: rgba(0, 0, 0, 0.85);\n background: rgba(77, 107, 254, 0.07);\n border: 1px solid rgba(77, 107, 254, 0.25);\n border-radius: 8px;\n padding: 8px 10px;\n white-space: pre-wrap;\n word-break: break-word;\n line-height: 1.5;\n}\n\n.dd3px29_reviewPlan {\n font-size: 12px;\n color: rgba(0, 0, 0, 0.75);\n background: #fafafa;\n border: 1px solid rgba(128, 128, 128, 0.18);\n border-radius: 8px;\n padding: 8px 10px;\n white-space: pre-wrap;\n word-break: break-word;\n line-height: 1.6;\n}\n\n.dd3px29_reviewList {\n display: flex;\n flex-direction: column;\n gap: 10px;\n max-height: 60vh;\n overflow-y: auto;\n padding-right: 4px;\n}\n\n.dd3px29_reviewItem {\n border: 1px solid rgba(128, 128, 128, 0.18);\n border-radius: 10px;\n padding: 10px 12px;\n background: #fdfdfd;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.dd3px29_reviewItemHead {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.dd3px29_reviewNode {\n font-size: 12px;\n font-weight: 600;\n color: rgba(0, 0, 0, 0.8);\n flex: 1;\n}\n\n.dd3px29_reviewSeq {\n font-size: 10px;\n color: rgba(0, 0, 0, 0.6);\n background: rgba(0, 0, 0, 0.06);\n border-radius: 999px;\n padding: 2px 8px;\n font-weight: 600;\n white-space: nowrap;\n}\n\n.dd3px29_reviewEndorsed {\n font-size: 10px;\n color: #c62828;\n background: rgba(198, 40, 40, 0.08);\n border-radius: 999px;\n padding: 2px 8px;\n font-weight: 600;\n}\n\n.dd3px29_reviewContent {\n font-size: 12px;\n color: rgba(0, 0, 0, 0.8);\n line-height: 1.6;\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.dd3px29_reviewActions {\n display: flex;\n justify-content: flex-end;\n}\n\n.dd3px29_reviewSupport {\n font-size: 11px;\n padding: 4px 12px;\n border-radius: 6px;\n border: 1px solid rgba(198, 40, 40, 0.45);\n background: #ffffff;\n color: #c62828;\n cursor: pointer;\n}\n\n.dd3px29_reviewSupport:hover {\n background: rgba(198, 40, 40, 0.08);\n}\n\n.dd3px29_reviewSupportDone {\n font-size: 11px;\n padding: 4px 12px;\n border-radius: 6px;\n border: 1px solid rgba(198, 40, 40, 0.3);\n background: rgba(198, 40, 40, 0.08);\n color: #c62828;\n cursor: default;\n}\n\n.dd3px29_reviewDimension {\n font-size: 10px;\n color: rgba(0, 0, 0, 0.55);\n background: rgba(0, 0, 0, 0.05);\n border-radius: 999px;\n padding: 2px 8px;\n white-space: nowrap;\n}\n\n.dd3px29_reviewQuote {\n font-size: 11px;\n color: rgba(0, 0, 0, 0.5);\n background: rgba(0, 0, 0, 0.04);\n border-left: 3px solid rgba(0, 0, 0, 0.15);\n padding: 4px 8px;\n border-radius: 0 6px 6px 0;\n line-height: 1.5;\n}\n\n.dd3px29_reviewPending {\n font-size: 10px;\n color: rgba(0, 0, 0, 0.5);\n background: rgba(0, 0, 0, 0.06);\n border-radius: 999px;\n padding: 2px 8px;\n font-weight: 500;\n}\n\n.dd3px29_reviewRejected {\n font-size: 10px;\n color: #2e7d32;\n background: rgba(46, 125, 50, 0.08);\n border-radius: 999px;\n padding: 2px 8px;\n font-weight: 600;\n}\n\n.dd3px29_reviewReject {\n font-size: 11px;\n padding: 4px 12px;\n border-radius: 6px;\n border: 1px solid rgba(46, 125, 50, 0.45);\n background: #ffffff;\n color: #2e7d32;\n cursor: pointer;\n margin-left: 8px;\n}\n\n.dd3px29_reviewReject:hover {\n background: rgba(46, 125, 50, 0.08);\n}\n\n.dd3px29_reviewRejectDone {\n font-size: 11px;\n padding: 4px 12px;\n border-radius: 6px;\n border: 1px solid rgba(46, 125, 50, 0.3);\n background: rgba(46, 125, 50, 0.08);\n color: #2e7d32;\n cursor: default;\n margin-left: 8px;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .dd3px29_breathing {\n animation: none;\n border-color: rgba(77, 107, 254, 0.6);\n }\n}\n\n.dd3px29_reviewPassBadge {\n font-size: 10px;\n color: rgba(0, 0, 0, 0.55);\n background: rgba(0, 0, 0, 0.05);\n border-radius: 999px;\n padding: 2px 8px;\n font-weight: 600;\n margin-right: 6px;\n}\n\n.dd3px29_reviewEvidence {\n font-size: 11px;\n color: rgba(0, 0, 0, 0.6);\n background: rgba(25, 118, 210, 0.06);\n border-left: 3px solid rgba(25, 118, 210, 0.35);\n padding: 4px 8px;\n border-radius: 0 6px 6px 0;\n line-height: 1.5;\n margin-top: 4px;\n white-space: pre-wrap;\n}\n\n.dd3px29_reviewEvidenceLabel {\n font-weight: 600;\n}\n\n.dd3px29_reviewRejectReason {\n font-size: 11px;\n color: rgba(0, 0, 0, 0.6);\n background: rgba(0, 0, 0, 0.03);\n border-left: 3px solid rgba(0, 0, 0, 0.2);\n padding: 4px 8px;\n border-radius: 0 6px 6px 0;\n line-height: 1.5;\n margin-top: 4px;\n}\n\n.dd3px29_reviewRejectBox {\n margin-top: 6px;\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.dd3px29_reviewRejectLabel {\n font-size: 11px;\n color: rgba(0, 0, 0, 0.6);\n}\n\n.dd3px29_reviewRejectInput {\n width: 100%;\n box-sizing: border-box;\n font-size: 12px;\n padding: 6px 8px;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 6px;\n background: rgba(255, 255, 255, 0.9);\n color: inherit;\n resize: vertical;\n min-height: 34px;\n font-family: inherit;\n}\n\n.dd3px29_reviewRejectActions {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n}\n\n.dd3px29_reviewImpactBar {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n margin-top: 6px;\n padding: 6px 8px;\n border-radius: 8px;\n background: rgba(0, 0, 0, 0.04);\n font-size: 11px;\n color: rgba(0, 0, 0, 0.65);\n}\n\n.dd3px29_reviewImpactWarn {\n background: rgba(237, 108, 2, 0.1);\n color: #b26a00;\n font-weight: 500;\n}\n\n.dd3px29_feedbackBar {\n position: fixed;\n right: 16px;\n bottom: 16px;\n z-index: 60;\n width: 300px;\n max-width: calc(100vw - 32px);\n background: #ffffff;\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-radius: 12px;\n box-shadow: 0 8px 28px rgba(0, 0, 0, 0.18);\n padding: 12px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.dd3px29_feedbackBarTitle {\n font-size: 12px;\n font-weight: 600;\n color: rgba(0, 0, 0, 0.8);\n line-height: 1.5;\n}\n\n.dd3px29_feedbackBarActions {\n display: flex;\n gap: 6px;\n}\n\n.dd3px29_feedbackRating {\n font-size: 12px;\n padding: 5px 10px;\n border-radius: 999px;\n border: 1px solid rgba(77, 107, 254, 0.4);\n background: rgba(77, 107, 254, 0.06);\n color: #4d6bfe;\n cursor: pointer;\n}\n\n.dd3px29_feedbackRating:hover {\n background: rgba(77, 107, 254, 0.14);\n}\n\n.dd3px29_feedbackBarNote {\n width: 100%;\n box-sizing: border-box;\n font-size: 12px;\n padding: 6px 8px;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 6px;\n background: rgba(255, 255, 255, 0.9);\n color: inherit;\n font-family: inherit;\n}\n\n.dd3px29_feedbackBarFooter {\n display: flex;\n justify-content: flex-end;\n}\n\n.dd3px29_feedbackSkip {\n font-size: 11px;\n padding: 3px 10px;\n border-radius: 6px;\n border: none;\n background: transparent;\n color: rgba(0, 0, 0, 0.5);\n cursor: pointer;\n}\n\n.dd3px29_feedbackSkip:hover {\n color: rgba(0, 0, 0, 0.8);\n}\n";
103
+ const css$1 = ".dd3px29_root {\n display: flex;\n height: 100%;\n min-height: 0;\n gap: 12px;\n padding: 12px;\n overflow: hidden;\n}\n\n/* ---------- main column ---------- */\n\n.dd3px29_main {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.dd3px29_header {\n display: flex;\n flex-direction: column;\n gap: 6px;\n flex: none;\n}\n\n.dd3px29_titleRow {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.dd3px29_meetingName {\n font-size: 15px;\n font-weight: 600;\n}\n\n.dd3px29_meetingSelect {\n font-size: 12px;\n padding: 2px 6px;\n border-radius: 6px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n max-width: 220px;\n}\n\n.dd3px29_badge {\n font-size: 11px;\n padding: 1px 8px;\n border-radius: 999px;\n border: 1px solid var(--dsw-alias-border-l2);\n color: var(--dsw-alias-label-secondary);\n}\n\n.dd3px29_round {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* 标题区「针锋相对」入口:可点击打开评审窗口 */\n.dd3px29_reviewBadge {\n font-size: 11px;\n padding: 2px 10px;\n border-radius: 999px;\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 45%, transparent);\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 6%, transparent);\n color: var(--dsw-alias-state-error-primary);\n cursor: pointer;\n font-weight: 600;\n transition: background 120ms ease;\n}\n\n.dd3px29_reviewBadge:hover {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 14%, transparent);\n}\n\n/* 右栏「分针记录」摘要卡片:可点击打开评审窗口 */\n.dd3px29_reviewCard {\n display: flex;\n flex-direction: column;\n gap: 4px;\n text-align: left;\n width: 100%;\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 25%, transparent);\n border-radius: 8px;\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 5%, transparent);\n padding: 8px 10px;\n cursor: pointer;\n transition: background 120ms ease;\n}\n\n.dd3px29_reviewCard:hover {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);\n}\n\n.dd3px29_reviewCardTitle {\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n line-height: 1.45;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.dd3px29_reviewCardMeta {\n font-size: 10px;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* 会议删除按钮:固定在右栏顶部操作条(不再挤在标题行最右,窄屏也能看到)。 */\n.dd3px29_meetingDelete {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 12px;\n padding: 4px 10px;\n border-radius: 6px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n opacity: 0.75;\n transition: opacity 120ms ease, border-color 120ms ease, color 120ms ease;\n}\n\n.dd3px29_meetingDelete:hover {\n opacity: 1;\n border-color: var(--dsw-alias-state-error-primary);\n color: var(--dsw-alias-state-error-primary);\n}\n\n.dd3px29_budgetRow {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.dd3px29_budgetLabel {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.dd3px29_budgetBar {\n width: 100px;\n height: 6px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsw-alias-label-secondary) 18%, transparent);\n overflow: hidden;\n}\n\n.dd3px29_budgetFill {\n height: 100%;\n border-radius: 999px;\n /* 蓝色强调用宿主的真实强调色令牌(浅色 #4176e6 / 深色 deepseek-450),\n 不用 --dsw-alias-brand-primary —— 后者是随主题翻转的近黑/近白单色。 */\n background: var(--dsw-alias-brand-primary-new-colorprimary-new-color);\n}\n\n.dd3px29_budgetFillTokens {\n height: 100%;\n border-radius: 999px;\n background: var(--dsw-alias-state-success-primary);\n}\n\n.dd3px29_budgetValue {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n font-variant-numeric: tabular-nums;\n}\n\n.dd3px29_decisionBanner {\n display: flex;\n align-items: baseline;\n gap: 8px;\n flex-wrap: wrap;\n padding: 6px 10px;\n border-radius: 8px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 12%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 40%, transparent);\n}\n\n.dd3px29_decisionTag {\n font-size: 11px;\n font-weight: 600;\n color: var(--dsw-alias-state-warn-primary);\n flex: none;\n}\n\n.dd3px29_decisionQuestion {\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n}\n\n/* ---------- topology canvas ---------- */\n\n.dd3px29_canvas {\n position: relative;\n flex: 1;\n min-width: 320px;\n min-height: 420px;\n border-radius: 12px;\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l2);\n overflow: hidden;\n}\n\n/* A1 图例:把「这张图在当前模式下该怎么读」直接写在画布上。\n 起因是红队评审的两条认定缺陷(gpt-1 / claude-2):同一张通道图在\n 主持人统筹 / 多模型平等 / 针锋相对三种模式下含义完全不同,不写清楚,\n 用户会把它读成一张\"控制流程的路径图\"。\n pointer-events:none —— 它只是说明文字,绝不能挡住节点的拖拽拉线。\n 位置放左上角:主持人节点锚在左侧垂直居中(x≈10% w, y=50% h),\n 右上是连线最稀疏的区域。 */\n.dd3px29_canvasLegend {\n position: absolute;\n top: 10px;\n left: 12px;\n z-index: 3;\n max-width: 300px;\n /* 右侧留出叉号的位置,避免文字压到关闭按钮下面。 */\n padding: 7px 24px 7px 10px;\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n border: 1px solid var(--dsw-alias-border-l2);\n box-shadow: 0 1px 4px color-mix(in srgb, var(--dsw-alias-label-primary) 6%, transparent);\n pointer-events: none;\n line-height: 1.5;\n}\n\n/* 关闭按钮:父级是 pointer-events:none(图例绝不能挡住拖拽拉线),\n 所以这里必须单独把事件收回来。 */\n.dd3px29_canvasLegendClose {\n position: absolute;\n top: 4px;\n right: 4px;\n width: 16px;\n height: 16px;\n padding: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n line-height: 1;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n pointer-events: auto;\n}\n\n.dd3px29_canvasLegendClose:hover {\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n color: var(--dsw-alias-label-primary);\n}\n\n.dd3px29_canvasLegendTitle {\n font-size: 11px;\n font-weight: 600;\n color: var(--dsw-alias-label-secondary);\n margin-bottom: 2px;\n}\n\n.dd3px29_canvasLegendRow {\n font-size: 11px;\n color: var(--dsw-alias-label-primary);\n}\n\n.dd3px29_canvasLegendHint {\n font-size: 10.5px;\n color: var(--dsw-alias-label-secondary);\n margin-top: 2px;\n}\n\n.dd3px29_edgeLayer {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n\n.dd3px29_edgeGroup {\n pointer-events: auto;\n cursor: pointer;\n}\n\n.dd3px29_edgeGroup path {\n pointer-events: stroke;\n}\n\n/* Hover delete affordance: a red circle with an X floating ABOVE the canvas\n at the edge's midpoint. It lives in a top layer (not the SVG, which sits\n under the nodes) so a midpoint that lands inside a node is never hidden.\n Revealed while the edge (or the button itself) is hovered. */\n.dd3px29_edgeRemove {\n position: absolute;\n transform: translate(-50%, -50%);\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: var(--dsw-alias-state-error-primary);\n border: 1.5px solid var(--dsw-alias-brand-primary);\n color: #ffffff;\n font-size: 12px;\n display: flex;\n align-items: center;\n justify-content: center;\n line-height: 1;\n padding: 0;\n cursor: pointer;\n z-index: 6;\n opacity: 0;\n pointer-events: none;\n transition: opacity 120ms ease;\n box-shadow: 0 1px 5px color-mix(in srgb, var(--dsw-alias-label-primary) 28%, transparent);\n}\n\n.dd3px29_edgeRemoveActive {\n opacity: 1;\n pointer-events: auto;\n}\n\n.dd3px29_edgeRemove:hover {\n opacity: 1;\n pointer-events: auto;\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 85%, transparent);\n}\n\n/* 专家↔专家的普通边:此前用 border-l1,在浅色底上几乎看不见。\n * 换成 label-secondary 并统一 round 端点,曲线交汇处不再有硬角。 */\n.dd3px29_edgeForward {\n stroke: var(--dsw-alias-label-secondary);\n stroke-width: 1.6;\n stroke-linecap: round;\n}\n\n.dd3px29_edgeBidirectional {\n stroke: color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 70%, transparent);\n stroke-width: 1.6;\n}\n\n.dd3px29_edgeArrow {\n fill: var(--dsw-alias-border-l1);\n}\n\n.dd3px29_edgeSynthetic {\n stroke: var(--dsw-alias-border-l2);\n stroke-width: 1.3;\n stroke-dasharray: 4 4;\n}\n\n.dd3px29_edgeArrowSynthetic {\n fill: var(--dsw-alias-border-l2);\n}\n\n/* Role-based accents so the topology reads at a glance: captain is the single\n \"everything radiates from here\" hub (warm gold), aggregator is the sink\n (cool teal), and expert↔expert wires stay neutral grey. */\n.dd3px29_edgeCaptain {\n stroke: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 85%, transparent);\n stroke-width: 1.8;\n}\n\n.dd3px29_edgeAggregator {\n stroke: color-mix(in srgb, var(--dsw-alias-state-success-primary) 80%, transparent);\n stroke-width: 1.6;\n}\n\n.dd3px29_edgeSyntheticCaptain {\n stroke: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 45%, transparent);\n stroke-width: 1.4;\n stroke-dasharray: 4 4;\n}\n\n.dd3px29_edgeSyntheticAggregator {\n stroke: color-mix(in srgb, var(--dsw-alias-state-success-primary) 40%, transparent);\n stroke-width: 1.4;\n stroke-dasharray: 4 4;\n}\n\n.dd3px29_edgeArrowCaptain {\n fill: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 90%, transparent);\n}\n\n.dd3px29_edgeArrowAggregator {\n fill: color-mix(in srgb, var(--dsw-alias-state-success-primary) 85%, transparent);\n}\n\n.dd3px29_edgeArrowSyntheticCaptain {\n fill: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 50%, transparent);\n}\n\n.dd3px29_edgeArrowSyntheticAggregator {\n fill: color-mix(in srgb, var(--dsw-alias-state-success-primary) 45%, transparent);\n}\n\n.dd3px29_dragLine {\n stroke: var(--dsw-alias-brand-primary-new-colorprimary-new-color);\n stroke-width: 1.6;\n stroke-dasharray: 5 4;\n pointer-events: none;\n}\n\n/* One-shot message pulse traveling from sender to receiver. */\n.dd3px29_msgPulse {\n position: absolute;\n left: 0;\n top: 0;\n width: 9px;\n height: 9px;\n border-radius: 50%;\n /* 与下方辉光同一强调蓝:此前填 brand-primary(随主题翻成近白),\n 深色下会变成\"蓝辉光围着近白圆点\"——不一致,且圆点在浅底上不可见。 */\n background: var(--dsw-alias-brand-primary-new-colorprimary-new-color);\n box-shadow: 0 0 10px 2px color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 70%, transparent);\n pointer-events: none;\n z-index: 4;\n animation: rt-msg-flow 1.1s ease-out forwards;\n}\n\n@keyframes rt-msg-flow {\n 0% {\n transform: translate(var(--rt-fx), var(--rt-fy)) translate(-50%, -50%);\n opacity: 1;\n }\n 100% {\n transform: translate(var(--rt-tx), var(--rt-ty)) translate(-50%, -50%);\n opacity: 0;\n }\n}\n\n.dd3px29_node {\n position: absolute;\n transform: translate(-50%, -50%);\n width: 84px;\n height: 84px;\n border-radius: 50%;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 2px;\n background: var(--dsw-alias-bg-layer-1);\n border: 1px solid var(--dsw-alias-border-l2);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n user-select: none;\n z-index: 2;\n}\n\n.dd3px29_captainNode {\n width: 108px;\n height: 96px;\n border-radius: 12px;\n background: #202124;\n border: none;\n box-shadow: 0 4px 14px color-mix(in srgb, var(--dsw-alias-label-primary) 25%, transparent);\n}\n\n/* 黑块上的文字必须用固定白色:`label-secondary` 是\"浅底上的次要文字\",\n * 在 #202124 上会变成低对比的灰,读不出来。 */\n.dd3px29_captainNode .dd3px29_nodeLabel {\n color: #ffffff;\n font-size: 12px;\n font-weight: 600;\n text-align: center;\n line-height: 1.4;\n}\n\n.dd3px29_aggregatorNode {\n width: 84px;\n height: 84px;\n border-radius: 14px;\n background: var(--dsw-alias-bg-layer-2);\n border: 1.5px dashed var(--dsw-alias-border-l2);\n}\n\n/* 节点标签:允许两行。\n *\n * 68px 的圆形节点里,单行 + 62px 会把任何超过 5 个字符的名字吃掉\n * (`expert-lifecycle` → `expert-life…`)。两行 + 10px 能放下 `lifecycle`\n * 这类 9–13 字符的名字;超过两行才截断(`-webkit-line-clamp`)。\n * 完整 key 仍由节点的 title 提供。 */\n.dd3px29_nodeLabel {\n font-size: 11px;\n color: var(--dsw-alias-label-primary);\n text-align: center;\n line-height: 1.25;\n max-width: 78px;\n overflow: hidden;\n overflow-wrap: anywhere;\n white-space: normal;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n}\n\n/* Captain/aggregator labels are longer (\"DeepSeek · 主持\", \"汇聚网关\") and are\n clipped into \"DeepSee…\" by the 62px default. Give them room to wrap to two\n lines so the full name shows — the truncation was hiding the final \"K\" and\n broke the reading of the host's name. */\n.dd3px29_captainNode .dd3px29_nodeLabel,\n.dd3px29_aggregatorNode .dd3px29_nodeLabel {\n max-width: 100px;\n white-space: normal;\n overflow-wrap: break-word;\n word-break: break-word;\n}\n\n/* 头像:纯圆图,不再套一圈 inset 灰边 —— 那圈边与节点自身的边框叠在一起,\n * 看起来像\"框里还有一个框\"。 */\n.dd3px29_avatar {\n width: 36px;\n height: 36px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n flex: none;\n overflow: hidden;\n}\n\n/* 厂商 logo 图:圆形裁切 + contain 防切边,品牌色底垫在透明图后面。 */\n.dd3px29_avatarImg {\n width: 100%;\n height: 100%;\n border-radius: 50%;\n object-fit: contain;\n padding: 2px;\n box-sizing: border-box;\n}\n\n.dd3px29_avatarAbbr {\n color: #ffffff;\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.3px;\n line-height: 1;\n}\n\n/* A2:节点状态如实显示持久子代理的生命周期。默认中性灰(空闲/就绪/未唤醒\n 都不代表节点坏了),只有真的在跑才用绿色高亮。\n 节点只有 68px 宽,所以这里强制单行 + 省略号:状态标签永远不能换行,\n 否则文字会溢出圆形卡片、和连线叠在一起(\"就绪(可唤醒)\"就是这么翻车的)。\n 完整语义交给 title(activityLifecycleHint)与右栏列表的悬停提示。 */\n.dd3px29_nodeMeta {\n font-size: 10px;\n color: var(--dsw-alias-label-secondary);\n min-height: 12px;\n max-width: calc(100% - 4px);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.dd3px29_nodeMetaWorking {\n color: var(--dsw-alias-state-success-primary);\n}\n\n/* A node that was added then removed: keep it visible so the topology still\n shows which models participated, but degrade it (greyed, no pulse, muted). */\n.dd3px29_nodeRemoved {\n background: var(--dsw-alias-bg-layer-2);\n border-color: var(--dsw-alias-border-l2);\n box-shadow: none;\n opacity: 0.72;\n}\n\n.dd3px29_nodeRemoved .dd3px29_nodeLabel {\n color: var(--dsw-alias-label-secondary);\n text-decoration: line-through;\n}\n\n.dd3px29_nodeRemoved .dd3px29_avatar {\n filter: grayscale(0.8);\n opacity: 0.85;\n}\n\n/* Breathing light: low-opacity pulse, pure CSS — zero tokens. */\n.dd3px29_breathing {\n animation: roundtable-pulse 1.6s ease-in-out infinite;\n}\n\n@keyframes roundtable-pulse {\n 0%, 100% {\n box-shadow: 0 0 0 0 color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 28%, transparent), 0 2px 8px color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 75%, transparent);\n }\n 50% {\n box-shadow: 0 0 0 9px color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 0%, transparent), 0 2px 8px color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 45%, transparent);\n }\n}\n\n/* Connector ports: small hit-dots on the node rim that a connector grows out\n of (a Dify-style workflow port). Four per node; each is a 10px circle that\n brightens on hover to signal \"drag from here to wire an edge\". The dots sit\n outside the node circle so the wire visibly starts at the expert's edge. */\n.dd3px29_port {\n position: absolute;\n width: 11px;\n height: 11px;\n border-radius: 50%;\n background: var(--dsw-alias-bg-layer-1);\n border: 2px solid var(--dsw-alias-brand-primary-new-colorprimary-new-color);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 15%, transparent);\n cursor: crosshair;\n opacity: 0;\n transition: opacity 120ms ease, transform 120ms ease, box-shadow 120ms ease;\n z-index: 4;\n}\n\n.dd3px29_node:hover .dd3px29_port,\n.dd3px29_port:focus-visible {\n opacity: 1;\n}\n\n.dd3px29_port:hover {\n transform: scale(1.25);\n box-shadow: 0 0 0 4px color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 28%, transparent);\n /* 与描边同一强调蓝:此前填 brand-primary,深色下会与蓝色描边自相矛盾。 */\n background: var(--dsw-alias-brand-primary-new-colorprimary-new-color);\n}\n\n/* Placement: centered on each rim, slightly outside the 68px circle. */\n.dd3px29_portN { top: -7px; left: calc(50% - 5.5px); }\n.dd3px29_portE { right: -7px; top: calc(50% - 5.5px); }\n.dd3px29_portS { bottom: -7px; left: calc(50% - 5.5px); }\n.dd3px29_portW { left: -7px; top: calc(50% - 5.5px); }\n\n/* Larger captain/aggregator rims need the ports pushed a touch further out. */\n.dd3px29_captainNode .dd3px29_portN { top: -9px; }\n.dd3px29_captainNode .dd3px29_portS { bottom: -9px; }\n.dd3px29_captainNode .dd3px29_portE { right: -9px; }\n.dd3px29_captainNode .dd3px29_portW { left: -9px; }\n.dd3px29_aggregatorNode .dd3px29_portN { top: -8px; }\n.dd3px29_aggregatorNode .dd3px29_portS { bottom: -8px; }\n.dd3px29_aggregatorNode .dd3px29_portE { right: -8px; }\n.dd3px29_aggregatorNode .dd3px29_portW { left: -8px; }\n\n/* While a connector is being dragged, keep every port visible so the user can\n see exactly where they may drop — ports otherwise only appear on hover. */\n.dd3px29_canvasDragging .dd3px29_port {\n opacity: 0.6;\n}\n\n/* ---------- gateway digest ---------- */\n\n.dd3px29_digest {\n flex: none;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-2);\n font-size: 12px;\n}\n\n.dd3px29_digest summary {\n cursor: pointer;\n padding: 6px 10px;\n font-weight: 600;\n color: var(--dsw-alias-label-secondary);\n}\n\n.dd3px29_digestBody {\n margin: 0;\n padding: 0 10px 10px;\n max-height: 180px;\n overflow: auto;\n font-family: inherit;\n white-space: pre-wrap;\n word-break: break-word;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* ---------- sidebar ---------- */\n\n.dd3px29_sidebar {\n width: 272px;\n flex: none;\n display: flex;\n flex-direction: column;\n gap: 12px;\n overflow-y: auto;\n padding-right: 2px;\n}\n\n/* 右栏顶部操作条:删除会议 + 待办徽标(固定位置,窄屏可见)。 */\n.dd3px29_sidebarActions {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n flex: none;\n}\n\n.dd3px29_pendingBadge {\n font-size: 11px;\n color: var(--dsw-alias-state-warn-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 50%, transparent);\n border-radius: 999px;\n padding: 2px 8px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n min-width: 0;\n}\n\n/* 专家行\"待移除\"标记(用户已删除、主持人下一轮执行)。 */\n.dd3px29_agentQueuedRemove {\n font-size: 10px;\n color: var(--dsw-alias-state-warn-primary);\n flex: none;\n font-weight: 600;\n}\n\n.dd3px29_panel {\n flex: none;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-2);\n overflow: hidden;\n}\n\n.dd3px29_panelTitleRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 10px;\n border-bottom: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n}\n\n/* 面板标题是右栏的\"目录\":用正文色 + 字距,而不是与说明文字同级的灰色。 */\n.dd3px29_panelTitle {\n font-size: 12px;\n font-weight: 650;\n letter-spacing: 0.01em;\n color: var(--dsw-alias-label-primary);\n}\n\n.dd3px29_panelAdd {\n width: 18px;\n height: 18px;\n border-radius: 50%;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n line-height: 16px;\n text-align: center;\n padding: 0;\n cursor: pointer;\n flex: none;\n}\n\n.dd3px29_panelAdd:hover {\n border-color: var(--dsw-alias-brand-primary-new-colorprimary-new-color);\n color: var(--dsw-alias-brand-primary-new-colorprimary-new-color);\n}\n\n.dd3px29_panelBody {\n padding: 10px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n max-height: 220px;\n overflow-y: auto;\n}\n\n.dd3px29_agentRow {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.dd3px29_agentAvatar {\n width: 26px;\n height: 26px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n flex: none;\n}\n\n.dd3px29_agentAbbr {\n color: #ffffff;\n font-size: 9px;\n font-weight: 700;\n}\n\n.dd3px29_agentInfo {\n flex: 1;\n min-width: 0;\n}\n\n.dd3px29_agentName {\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n\n.dd3px29_agentRole {\n font-size: 10px;\n color: var(--dsw-alias-label-secondary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.dd3px29_agentStatus {\n font-size: 10px;\n color: var(--dsw-alias-label-secondary);\n flex: none;\n}\n\n.dd3px29_agentStatusWorking {\n font-size: 10px;\n color: var(--dsw-alias-state-success-primary);\n flex: none;\n font-weight: 600;\n}\n\n.dd3px29_agentRowRemoved {\n opacity: 0.6;\n}\n\n.dd3px29_agentRowRemoved .dd3px29_avatar,\n.dd3px29_agentRowRemoved .dd3px29_agentAvatar {\n filter: grayscale(0.8);\n}\n\n.dd3px29_agentRowRemoved .dd3px29_agentName {\n color: var(--dsw-alias-label-secondary);\n text-decoration: line-through;\n}\n\n.dd3px29_agentStatusRemoved {\n font-size: 10px;\n color: var(--dsw-alias-label-secondary);\n font-style: italic;\n flex: none;\n}\n\n.dd3px29_taskRow {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.dd3px29_taskToggle {\n display: flex;\n align-items: center;\n gap: 6px;\n background: none;\n border: none;\n padding: 0;\n cursor: pointer;\n text-align: left;\n border-radius: 4px;\n}\n\n.dd3px29_taskToggle:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 8%, transparent);\n}\n\n.dd3px29_taskChevron {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n width: 12px;\n flex: none;\n}\n\n.dd3px29_taskKey {\n font-size: 11px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n\n/* Collapsed: always a single line with an ellipsis, regardless of length. */\n.dd3px29_taskRole {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n line-height: 1.35;\n}\n\n/* Expanded: full role, wrapped. */\n.dd3px29_taskRoleExpanded {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n line-height: 1.45;\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.dd3px29_panelEmpty {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n line-height: 1.5;\n}\n\n.dd3px29_logRow {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding-bottom: 6px;\n border-bottom: 1px dashed var(--dsw-alias-border-l2);\n}\n\n.dd3px29_logRow:last-child {\n border-bottom: none;\n padding-bottom: 0;\n}\n\n.dd3px29_logHead {\n display: flex;\n justify-content: space-between;\n gap: 6px;\n}\n\n.dd3px29_logFrom {\n font-size: 10px;\n font-weight: 600;\n color: color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 90%, transparent);\n}\n\n.dd3px29_logTime {\n font-size: 10px;\n color: var(--dsw-alias-label-secondary);\n font-variant-numeric: tabular-nums;\n}\n\n.dd3px29_logText {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n line-height: 1.45;\n}\n\n/* ---------- context menu ---------- */\n\n.dd3px29_contextMenu {\n position: fixed;\n z-index: 1000;\n min-width: 170px;\n background: var(--dsw-alias-bg-layer-1);\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n box-shadow: 0 6px 20px color-mix(in srgb, var(--dsw-alias-label-primary) 14%, transparent);\n padding: 4px;\n display: flex;\n flex-direction: column;\n}\n\n.dd3px29_contextMenuTitle {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n padding: 4px 10px;\n border-bottom: 1px solid var(--dsw-alias-border-l2);\n margin-bottom: 4px;\n}\n\n.dd3px29_contextMenuItem,\n.dd3px29_contextMenuItemDanger {\n border: none;\n background: none;\n text-align: left;\n font-size: 13px;\n padding: 7px 10px;\n border-radius: 6px;\n cursor: pointer;\n color: var(--dsw-alias-label-primary);\n}\n\n.dd3px29_contextMenuItem:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 10%, transparent);\n}\n\n.dd3px29_contextMenuItemDanger {\n color: var(--dsw-alias-state-error-primary);\n}\n\n.dd3px29_contextMenuItemDanger:hover {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 8%, transparent);\n}\n\n/* ---------- empty / error ---------- */\n\n.dd3px29_emptyState {\n height: 100%;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 10px;\n padding: 24px;\n text-align: center;\n}\n\n.dd3px29_emptyTitle {\n font-size: 15px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n\n.dd3px29_emptyHint {\n font-size: 13px;\n color: var(--dsw-alias-label-secondary);\n max-width: 420px;\n line-height: 1.6;\n}\n\n.dd3px29_fetchFailed {\n font-size: 11px;\n color: var(--dsw-alias-state-error-primary);\n}\n\n/* Connect-result toast: a short-lived banner at the top of the topology\n canvas telling the user whether a dragged wire landed. Without it a failed\n edge.dd3px29_add (e.dd3px29_g. wiring a removed node) was silently swallowed — the user saw\n nothing and assumed the drag did nothing. White card, black text, no tilt:\n the label sits on a single centered baseline. */\n.dd3px29_toast {\n position: absolute;\n top: 12px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 6;\n max-width: 90%;\n background: var(--dsw-alias-bg-layer-1);\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n padding: 8px 16px;\n box-shadow: 0 4px 14px color-mix(in srgb, var(--dsw-alias-label-primary) 18%, transparent);\n pointer-events: none;\n display: flex;\n align-items: center;\n justify-content: center;\n line-height: 1.4;\n text-align: center;\n font-variant-numeric: tabular-nums;\n}\n\n.dd3px29_toastOk {\n font-size: 12.5px;\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n.dd3px29_toastErr {\n font-size: 12.5px;\n color: var(--dsw-alias-state-error-primary);\n font-weight: 500;\n}\n\n/* ---------- expert management modal (panel \"+\" buttons) ---------- */\n\n.dd3px29_manageOverlay {\n position: fixed;\n inset: 0;\n z-index: 1200;\n background: var(--dsw-alias-bg-overlay);\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.dd3px29_manageModal {\n width: 480px;\n max-width: calc(100vw - 32px);\n max-height: calc(100vh - 48px);\n overflow-y: auto;\n background: var(--dsw-alias-bg-layer-1);\n border-radius: 12px;\n box-shadow: 0 12px 40px color-mix(in srgb, var(--dsw-alias-label-primary) 22%, transparent);\n padding: 16px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.dd3px29_manageTitle {\n font-size: 14px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n\n.dd3px29_managePendingNote {\n font-size: 11px;\n color: var(--dsw-alias-state-warn-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 12%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 45%, transparent);\n border-radius: 8px;\n padding: 6px 10px;\n line-height: 1.5;\n}\n\n.dd3px29_manageSectionTitle {\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-label-secondary);\n margin-top: 2px;\n}\n\n.dd3px29_manageList {\n display: flex;\n flex-direction: column;\n gap: 6px;\n max-height: 220px;\n overflow-y: auto;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n padding: 8px;\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.dd3px29_manageRow {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.dd3px29_managePendingAvatar {\n width: 26px;\n height: 26px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n flex: none;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 25%, transparent);\n color: var(--dsw-alias-state-warn-primary);\n font-size: 14px;\n font-weight: 700;\n}\n\n.dd3px29_manageStatusTag {\n font-size: 10px;\n color: var(--dsw-alias-label-secondary);\n flex: none;\n}\n\n.dd3px29_managePendingTag {\n font-size: 10px;\n color: var(--dsw-alias-state-warn-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 15%, transparent);\n border-radius: 999px;\n padding: 2px 8px;\n flex: none;\n font-weight: 600;\n}\n\n.dd3px29_manageRemoveBtn {\n font-size: 11px;\n padding: 2px 10px;\n border-radius: 6px;\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 40%, transparent);\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-state-error-primary);\n cursor: pointer;\n flex: none;\n}\n\n.dd3px29_manageRemoveBtn:hover {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 8%, transparent);\n}\n\n.dd3px29_manageForm {\n display: flex;\n flex-direction: column;\n gap: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n padding: 10px;\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.dd3px29_manageFormRow {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.dd3px29_manageLabel {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n width: 40px;\n flex: none;\n}\n\n.dd3px29_manageInput {\n flex: 1;\n min-width: 0;\n font-size: 12px;\n padding: 4px 8px;\n border-radius: 6px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n}\n\n.dd3px29_manageSelect {\n flex: 1;\n min-width: 0;\n font-size: 12px;\n padding: 3px 6px;\n border-radius: 6px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n}\n\n.dd3px29_manageAddBtn {\n align-self: flex-end;\n font-size: 12px;\n padding: 4px 14px;\n border-radius: 6px;\n border: none;\n background: var(--dsw-alias-brand-primary);\n /* 填充的反色,而不是写死的白字:brand-primary 随主题翻转(浅色近黑 / 深色近白),\n 写死 #ffffff 会在深色下变成\"白字压近白底\"。该令牌是宿主 primitives 标准的\n \"填充上的文字色\"(浅色 #fff / 深色 #0f1115),语义正确。 */\n color: var(--dsw-alias-label-primary-foreground);\n cursor: pointer;\n}\n\n.dd3px29_manageAddBtn:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 85%, transparent);\n}\n\n.dd3px29_manageFooter {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n border-top: 1px solid var(--dsw-alias-border-l2);\n padding-top: 10px;\n}\n\n.dd3px29_manageHint {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n line-height: 1.5;\n}\n\n.dd3px29_manageCloseBtn {\n font-size: 12px;\n padding: 4px 14px;\n border-radius: 6px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n flex: none;\n}\n\n.dd3px29_manageCloseBtn:hover {\n border-color: var(--dsw-alias-brand-primary-new-colorprimary-new-color);\n color: var(--dsw-alias-brand-primary-new-colorprimary-new-color);\n}\n\n/* ---------- 右栏面板可见性(R3:设置页点圆圈决定显示) ---------- */\n\n/* 被用户关掉的面板不渲染(右栏因此变短,拓扑区自然变大)。 */\n.dd3px29_panelHidden {\n display: none !important;\n}\n\n/* ---------- knowledge base (阅览版) ---------- */\n\n/* skill 传递方式的简短标注(与面板标题同一行,保持界面工整)。 */\n.dd3px29_skillDeliveryLabel {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n font-weight: 400;\n}\n\n.dd3px29_kbPathRow {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n background: color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 7%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 25%, transparent);\n border-radius: 6px;\n padding: 5px 8px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n word-break: break-all;\n}\n\n.dd3px29_kbPathForm {\n display: flex;\n flex-direction: column;\n gap: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n padding: 10px;\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.dd3px29_kbChangedRow {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n user-select: none;\n}\n\n.dd3px29_switchInput {\n accent-color: var(--dsw-alias-brand-primary-new-colorprimary-new-color);\n cursor: pointer;\n}\n\n.dd3px29_kbList {\n display: flex;\n flex-direction: column;\n gap: 4px;\n max-height: 240px;\n overflow-y: auto;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n padding: 8px;\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.dd3px29_kbRow {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n}\n\n.dd3px29_kbKindDir,\n.dd3px29_kbKindFile {\n font-size: 13px;\n flex: none;\n opacity: 0.85;\n}\n\n.dd3px29_kbName {\n flex: 1;\n min-width: 0;\n color: var(--dsw-alias-label-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.dd3px29_kbMeta {\n font-size: 10px;\n color: var(--dsw-alias-label-secondary);\n flex: none;\n}\n\n.dd3px29_kbError {\n font-size: 12px;\n color: var(--dsw-alias-state-error-primary);\n line-height: 1.5;\n}\n\n/* ---------- 针锋相对评审弹窗(三区布局) ---------- */\n\n.dd3px29_reviewModal {\n width: 860px;\n max-width: calc(100vw - 32px);\n max-height: calc(100vh - 48px);\n overflow-y: auto;\n background: var(--dsw-alias-bg-layer-1);\n border-radius: 12px;\n box-shadow: 0 12px 40px color-mix(in srgb, var(--dsw-alias-label-primary) 22%, transparent);\n padding: 16px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.dd3px29_reviewBody {\n display: flex;\n gap: 12px;\n min-height: 0;\n}\n\n.dd3px29_reviewLeft {\n flex: 0 0 300px;\n display: flex;\n flex-direction: column;\n gap: 6px;\n border-right: 1px solid var(--dsw-alias-border-l2);\n padding-right: 12px;\n max-height: 60vh;\n overflow-y: auto;\n}\n\n.dd3px29_reviewRight {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.dd3px29_reviewSectionTitle {\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-label-secondary);\n}\n\n.dd3px29_reviewQuestion {\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n background: color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 7%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 25%, transparent);\n border-radius: 8px;\n padding: 8px 10px;\n white-space: pre-wrap;\n word-break: break-word;\n line-height: 1.5;\n}\n\n.dd3px29_reviewPlan {\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n padding: 8px 10px;\n white-space: pre-wrap;\n word-break: break-word;\n line-height: 1.6;\n}\n\n.dd3px29_reviewList {\n display: flex;\n flex-direction: column;\n gap: 10px;\n max-height: 60vh;\n overflow-y: auto;\n padding-right: 4px;\n}\n\n.dd3px29_reviewItem {\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n padding: 10px 12px;\n background: var(--dsw-alias-bg-layer-2);\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.dd3px29_reviewItemHead {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.dd3px29_reviewNode {\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n flex: 1;\n}\n\n.dd3px29_reviewSeq {\n font-size: 10px;\n color: var(--dsw-alias-label-secondary);\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 6%, transparent);\n border-radius: 999px;\n padding: 2px 8px;\n font-weight: 600;\n white-space: nowrap;\n}\n\n.dd3px29_reviewEndorsed {\n font-size: 10px;\n color: var(--dsw-alias-state-success-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 8%, transparent);\n border-radius: 999px;\n padding: 2px 8px;\n font-weight: 600;\n}\n\n.dd3px29_reviewContent {\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n line-height: 1.6;\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.dd3px29_reviewActions {\n display: flex;\n justify-content: flex-end;\n}\n\n/* 表态三态的语义配色:认定(endorsed / support)= 绿系,驳回(rejected / reject)= 红系。\n 与针锋 Crossfire 插件的约定保持一致(认定=绿、驳回=红),\n 避免同一用户在两个插件之间切换时反向读数。不要改回去。 */\n.dd3px29_reviewSupport {\n font-size: 11px;\n padding: 4px 12px;\n border-radius: 6px;\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-success-primary) 45%, transparent);\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-state-success-primary);\n cursor: pointer;\n}\n\n.dd3px29_reviewSupport:hover {\n background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 8%, transparent);\n}\n\n.dd3px29_reviewSupportDone {\n font-size: 11px;\n padding: 4px 12px;\n border-radius: 6px;\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-success-primary) 30%, transparent);\n background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 8%, transparent);\n color: var(--dsw-alias-state-success-primary);\n cursor: default;\n}\n\n.dd3px29_reviewDimension {\n font-size: 10px;\n color: var(--dsw-alias-label-secondary);\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 5%, transparent);\n border-radius: 999px;\n padding: 2px 8px;\n white-space: nowrap;\n}\n\n.dd3px29_reviewQuote {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 4%, transparent);\n border-left: 3px solid var(--dsw-alias-border-l2);\n padding: 4px 8px;\n border-radius: 0 6px 6px 0;\n line-height: 1.5;\n}\n\n.dd3px29_reviewPending {\n font-size: 10px;\n color: var(--dsw-alias-label-secondary);\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 6%, transparent);\n border-radius: 999px;\n padding: 2px 8px;\n font-weight: 500;\n}\n\n.dd3px29_reviewRejected {\n font-size: 10px;\n color: var(--dsw-alias-state-error-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 8%, transparent);\n border-radius: 999px;\n padding: 2px 8px;\n font-weight: 600;\n}\n\n.dd3px29_reviewReject {\n font-size: 11px;\n padding: 4px 12px;\n border-radius: 6px;\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 45%, transparent);\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-state-error-primary);\n cursor: pointer;\n margin-left: 8px;\n}\n\n.dd3px29_reviewReject:hover {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 8%, transparent);\n}\n\n.dd3px29_reviewRejectDone {\n font-size: 11px;\n padding: 4px 12px;\n border-radius: 6px;\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 30%, transparent);\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 8%, transparent);\n color: var(--dsw-alias-state-error-primary);\n cursor: default;\n margin-left: 8px;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .dd3px29_breathing {\n animation: none;\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 60%, transparent);\n }\n}\n\n.dd3px29_reviewPassBadge {\n font-size: 10px;\n color: var(--dsw-alias-label-secondary);\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 5%, transparent);\n border-radius: 999px;\n padding: 2px 8px;\n font-weight: 600;\n margin-right: 6px;\n}\n\n.dd3px29_reviewEvidence {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n background: color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 6%, transparent);\n border-left: 3px solid color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 35%, transparent);\n padding: 4px 8px;\n border-radius: 0 6px 6px 0;\n line-height: 1.5;\n margin-top: 4px;\n white-space: pre-wrap;\n}\n\n.dd3px29_reviewEvidenceLabel {\n font-weight: 600;\n}\n\n.dd3px29_reviewRejectReason {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 3%, transparent);\n border-left: 3px solid var(--dsw-alias-border-l1);\n padding: 4px 8px;\n border-radius: 0 6px 6px 0;\n line-height: 1.5;\n margin-top: 4px;\n}\n\n.dd3px29_reviewRejectBox {\n margin-top: 6px;\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.dd3px29_reviewRejectLabel {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.dd3px29_reviewRejectInput {\n width: 100%;\n box-sizing: border-box;\n font-size: 12px;\n padding: 6px 8px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-1);\n color: inherit;\n resize: vertical;\n min-height: 34px;\n font-family: inherit;\n}\n\n.dd3px29_reviewRejectActions {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n}\n\n.dd3px29_reviewImpactBar {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n margin-top: 6px;\n padding: 6px 8px;\n border-radius: 8px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 4%, transparent);\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.dd3px29_reviewImpactWarn {\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 10%, transparent);\n color: var(--dsw-alias-state-warn-primary);\n font-weight: 500;\n}\n\n.dd3px29_feedbackBar {\n position: fixed;\n right: 16px;\n bottom: 16px;\n z-index: 60;\n width: 300px;\n max-width: calc(100vw - 32px);\n background: var(--dsw-alias-bg-layer-1);\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 12px;\n box-shadow: 0 8px 28px color-mix(in srgb, var(--dsw-alias-label-primary) 18%, transparent);\n padding: 12px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.dd3px29_feedbackBarTitle {\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n line-height: 1.5;\n}\n\n.dd3px29_feedbackBarActions {\n display: flex;\n gap: 6px;\n}\n\n.dd3px29_feedbackRating {\n font-size: 12px;\n padding: 5px 10px;\n border-radius: 999px;\n border: 1px solid color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 40%, transparent);\n background: color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 6%, transparent);\n color: var(--dsw-alias-brand-primary-new-colorprimary-new-color);\n cursor: pointer;\n}\n\n.dd3px29_feedbackRating:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 14%, transparent);\n}\n\n.dd3px29_feedbackBarNote {\n width: 100%;\n box-sizing: border-box;\n font-size: 12px;\n padding: 6px 8px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-1);\n color: inherit;\n font-family: inherit;\n}\n\n.dd3px29_feedbackBarFooter {\n display: flex;\n justify-content: flex-end;\n}\n\n.dd3px29_feedbackSkip {\n font-size: 11px;\n padding: 3px 10px;\n border-radius: 6px;\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n}\n\n.dd3px29_feedbackSkip:hover {\n color: var(--dsw-alias-label-primary);\n}\n";
104
104
  const classMap$1 = {
105
105
  "root": "dd3px29_root",
106
106
  "main": "dd3px29_main",
@@ -298,7 +298,9 @@ var RoundTableView_module_css_default = classMap$1;
298
298
 
299
299
  //#endregion
300
300
  //#region src/client/RoundTableView.tsx
301
- const NODE_RADIUS = 34;
301
+ /** 节点半径(px):与 `.node` 的 84px 直径一致。连线端点、环形最小半径都靠它,
302
+ * 改节点尺寸时必须同步改这里,否则线会插进节点内部。 */
303
+ const NODE_RADIUS = 42;
302
304
  /** Provider → brand avatar (logo image if bundled, else abbreviation + brand color). */
303
305
  const PROVIDER_BRAND = [
304
306
  {
@@ -429,10 +431,16 @@ function arrowPoints(x, y, angle, size = 7) {
429
431
  };
430
432
  return `${tip.x},${tip.y} ${left.x},${left.y} ${right.x},${right.y}`;
431
433
  }
434
+ const ZERO_FAN = {
435
+ fromDegree: 0,
436
+ fromIndex: 0,
437
+ toDegree: 0,
438
+ toIndex: 0
439
+ };
432
440
  /** Quadratic-bezier geometry for one edge, shared by the renderer (which
433
441
  * draws the path) and the delete dot (which must sit on the curve's true
434
442
  * midpoint, i.e. the point at t=0.5 — not the control point). */
435
- function edgeCurveGeometry(edge, meeting, positions) {
443
+ function edgeCurveGeometry(edge, positions, fan) {
436
444
  const from = positions.get(edge.from);
437
445
  const to = positions.get(edge.to);
438
446
  if (from === void 0 || to === void 0) return null;
@@ -442,10 +450,7 @@ function edgeCurveGeometry(edge, meeting, positions) {
442
450
  const len = Math.max(1, Math.hypot(dx, dy));
443
451
  const ux = dx / len;
444
452
  const uy = dy / len;
445
- const fromDegree = meeting.edges.reduce((n, e) => n + (e.from === edge.from ? 1 : 0), 0);
446
- const fromIndex = meeting.edges.filter((e) => e.from === edge.from).findIndex((e) => e.id === edge.id);
447
- const toDegree = meeting.edges.reduce((n, e) => n + (e.to === edge.to ? 1 : 0), 0);
448
- const toIndex = meeting.edges.filter((e) => e.to === edge.to).findIndex((e) => e.id === edge.id);
453
+ const { fromDegree, fromIndex, toDegree, toIndex } = fan;
449
454
  const hubFrom = edge.from === "captain" || edge.from === "aggregator";
450
455
  const hubTo = edge.to === "captain" || edge.to === "aggregator";
451
456
  const baseAngle = Math.atan2(uy, ux);
@@ -560,6 +565,8 @@ function RoundTableView(props) {
560
565
  const [fetchFailed, setFetchFailed] = (0, react.useState)(false);
561
566
  const [menu, setMenu] = (0, react.useState)(null);
562
567
  const [drag, setDrag] = (0, react.useState)(null);
568
+ const dragRef = (0, react.useRef)(null);
569
+ const [kbConnectFrom, setKbConnectFrom] = (0, react.useState)(null);
563
570
  const [toast, setToast] = (0, react.useState)(null);
564
571
  const [hoverEdgeId, setHoverEdgeId] = (0, react.useState)(null);
565
572
  const [showAll, setShowAll] = (0, react.useState)(true);
@@ -776,12 +783,37 @@ function RoundTableView(props) {
776
783
  }));
777
784
  };
778
785
  const positions = (0, react.useMemo)(() => meeting === void 0 ? /* @__PURE__ */ new Map() : layoutPositions(size, meeting), [meeting, size]);
786
+ const edgeFanTable = (0, react.useMemo)(() => {
787
+ const fans = /* @__PURE__ */ new Map();
788
+ if (meeting === void 0) return fans;
789
+ const outDegree = /* @__PURE__ */ new Map();
790
+ const inDegree = /* @__PURE__ */ new Map();
791
+ for (const edge of meeting.edges) {
792
+ outDegree.set(edge.from, (outDegree.get(edge.from) ?? 0) + 1);
793
+ inDegree.set(edge.to, (inDegree.get(edge.to) ?? 0) + 1);
794
+ }
795
+ const outCursor = /* @__PURE__ */ new Map();
796
+ const inCursor = /* @__PURE__ */ new Map();
797
+ for (const edge of meeting.edges) {
798
+ const fromIndex = outCursor.get(edge.from) ?? 0;
799
+ outCursor.set(edge.from, fromIndex + 1);
800
+ const toIndex = inCursor.get(edge.to) ?? 0;
801
+ inCursor.set(edge.to, toIndex + 1);
802
+ fans.set(edge.id, {
803
+ fromDegree: outDegree.get(edge.from) ?? 0,
804
+ fromIndex,
805
+ toDegree: inDegree.get(edge.to) ?? 0,
806
+ toIndex
807
+ });
808
+ }
809
+ return fans;
810
+ }, [meeting]);
779
811
  const edgeRemoveDots = (0, react.useMemo)(() => {
780
812
  if (meeting === void 0) return [];
781
813
  const dots = [];
782
814
  for (const edge of meeting.edges) {
783
815
  if (edge.id.startsWith("synthetic:")) continue;
784
- const geo = edgeCurveGeometry(edge, meeting, positions);
816
+ const geo = edgeCurveGeometry(edge, positions, edgeFanTable.get(edge.id) ?? ZERO_FAN);
785
817
  if (geo === null) continue;
786
818
  dots.push({
787
819
  id: edge.id,
@@ -790,25 +822,75 @@ function RoundTableView(props) {
790
822
  });
791
823
  }
792
824
  return dots;
793
- }, [meeting, positions]);
825
+ }, [
826
+ meeting,
827
+ positions,
828
+ edgeFanTable
829
+ ]);
830
+ const connectNodes = (0, react.useCallback)((from, to, meetingId) => {
831
+ if (from === to) return;
832
+ rpc("roundtable/edge.add", {
833
+ meetingId,
834
+ from,
835
+ to,
836
+ direction: "forward"
837
+ }).then((result) => {
838
+ if (result.ok) {
839
+ setToast({
840
+ kind: "ok",
841
+ text: `已连接 ${from} → ${to}`
842
+ });
843
+ refresh();
844
+ } else setToast({
845
+ kind: "err",
846
+ text: result.error?.message ?? "连线失败"
847
+ });
848
+ }).catch(() => setToast({
849
+ kind: "err",
850
+ text: "连线失败:无法连接会议服务"
851
+ }));
852
+ }, [rpc, refresh]);
853
+ const dragging = drag !== null;
794
854
  (0, react.useEffect)(() => {
795
- if (drag === null) return;
855
+ if (!dragging) return;
856
+ let frame = 0;
857
+ let pending = null;
858
+ const flush = () => {
859
+ frame = 0;
860
+ if (pending === null) return;
861
+ const next = pending;
862
+ pending = null;
863
+ setDrag((previous) => previous === null ? null : {
864
+ ...previous,
865
+ x: next.x,
866
+ y: next.y
867
+ });
868
+ };
796
869
  const move = (event) => {
797
870
  const rect = containerRef.current?.getBoundingClientRect();
798
871
  const x = rect === void 0 ? event.clientX : event.clientX - rect.left;
799
872
  const y = rect === void 0 ? event.clientY : event.clientY - rect.top;
800
- setDrag((previous) => previous === null ? null : {
801
- ...previous,
873
+ const current = dragRef.current;
874
+ if (current !== null) dragRef.current = {
875
+ ...current,
802
876
  x,
803
877
  y
804
- });
878
+ };
879
+ pending = {
880
+ x,
881
+ y
882
+ };
883
+ if (frame === 0) frame = window.requestAnimationFrame(flush);
805
884
  };
806
- const up = (event) => {
807
- const from = drag.from;
808
- const meetingId = drag.meetingId;
809
- const x = drag.x;
810
- const y = drag.y;
885
+ const up = () => {
886
+ const current = dragRef.current;
887
+ dragRef.current = null;
811
888
  setDrag(null);
889
+ if (current === null) return;
890
+ const from = current.from;
891
+ const meetingId = current.meetingId;
892
+ const x = current.x;
893
+ const y = current.y;
812
894
  let bestKey = null;
813
895
  let bestDist = Number.POSITIVE_INFINITY;
814
896
  const hitRadius = 70;
@@ -821,37 +903,21 @@ function RoundTableView(props) {
821
903
  }
822
904
  const to = bestKey;
823
905
  if (to === null || to === from || bestDist > hitRadius) return;
824
- rpc("roundtable/edge.add", {
825
- meetingId,
826
- from,
827
- to,
828
- direction: "forward"
829
- }).then((result) => {
830
- if (result.ok) {
831
- setToast({
832
- kind: "ok",
833
- text: `已连接 ${from} → ${to}`
834
- });
835
- refresh();
836
- } else setToast({
837
- kind: "err",
838
- text: result.error?.message ?? "连线失败"
839
- });
840
- }).catch(() => setToast({
841
- kind: "err",
842
- text: "连线失败:无法连接会议服务"
843
- }));
906
+ connectNodes(from, to, meetingId);
844
907
  };
845
908
  window.addEventListener("mousemove", move);
846
909
  window.addEventListener("mouseup", up);
847
910
  return () => {
848
911
  window.removeEventListener("mousemove", move);
849
912
  window.removeEventListener("mouseup", up);
913
+ if (frame !== 0) {
914
+ window.cancelAnimationFrame(frame);
915
+ flush();
916
+ }
850
917
  };
851
918
  }, [
852
- drag,
853
- refresh,
854
- rpc,
919
+ dragging,
920
+ connectNodes,
855
921
  positions
856
922
  ]);
857
923
  const activeMessages = (0, react.useMemo)(() => {
@@ -1169,10 +1235,7 @@ function RoundTableView(props) {
1169
1235
  const to = positions.get(edge.to);
1170
1236
  if (from === void 0 || to === void 0) return null;
1171
1237
  const synthetic = edge.id.startsWith("synthetic:");
1172
- const fromDegree = meeting.edges.reduce((n, e) => n + (e.from === edge.from ? 1 : 0), 0);
1173
- const fromIndex = meeting.edges.filter((e) => e.from === edge.from).findIndex((e) => e.id === edge.id);
1174
- const toDegree = meeting.edges.reduce((n, e) => n + (e.to === edge.to ? 1 : 0), 0);
1175
- const toIndex = meeting.edges.filter((e) => e.to === edge.to).findIndex((e) => e.id === edge.id);
1238
+ const { fromDegree, fromIndex, toDegree, toIndex } = edgeFanTable.get(edge.id) ?? ZERO_FAN;
1176
1239
  const dx = to.x - from.x;
1177
1240
  const dy = to.y - from.y;
1178
1241
  const len = Math.max(1, Math.hypot(dx, dy));
@@ -1241,12 +1304,31 @@ function RoundTableView(props) {
1241
1304
  const rect = containerRef.current?.getBoundingClientRect();
1242
1305
  const x = rect === void 0 ? clientX : clientX - rect.left;
1243
1306
  const y = rect === void 0 ? clientY : clientY - rect.top;
1244
- setDrag({
1307
+ const next = {
1245
1308
  from: key,
1246
1309
  meetingId: meeting.id,
1247
1310
  x,
1248
1311
  y
1249
- });
1312
+ };
1313
+ dragRef.current = next;
1314
+ setDrag(next);
1315
+ };
1316
+ const activatePortByKeyboard = () => {
1317
+ if (kbConnectFrom === null) {
1318
+ setKbConnectFrom(key);
1319
+ setToast({
1320
+ kind: "ok",
1321
+ text: `已选中连线起点 ${key},请在目标节点的端口上按 Enter(Esc 取消)`
1322
+ });
1323
+ return;
1324
+ }
1325
+ if (kbConnectFrom === key) {
1326
+ setKbConnectFrom(null);
1327
+ return;
1328
+ }
1329
+ const from = kbConnectFrom;
1330
+ setKbConnectFrom(null);
1331
+ connectNodes(from, key, meeting.id);
1250
1332
  };
1251
1333
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1252
1334
  "data-rt-key": key,
@@ -1262,7 +1344,7 @@ function RoundTableView(props) {
1262
1344
  left: point.x,
1263
1345
  top: point.y
1264
1346
  },
1265
- title: node === void 0 ? label : `${label}${node.role !== "" ? ` · ${node.role}` : ""}`,
1347
+ title: node === void 0 ? label : `${node.key}${node.role !== "" ? ` · ${node.role}` : ""}`,
1266
1348
  children: [
1267
1349
  brand !== null ? brandAvatar(brand, "node") : null,
1268
1350
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -1284,16 +1366,42 @@ function RoundTableView(props) {
1284
1366
  role: "button",
1285
1367
  tabIndex: 0,
1286
1368
  "aria-label": "connect",
1369
+ "aria-pressed": kbConnectFrom === key,
1287
1370
  "data-port-dir": dir,
1288
1371
  onMouseDown: (event) => {
1289
1372
  event.preventDefault();
1290
1373
  event.stopPropagation();
1291
1374
  startDrag(event.clientX, event.clientY);
1375
+ },
1376
+ onKeyDown: (event) => {
1377
+ if (event.key === "Escape") {
1378
+ if (kbConnectFrom !== null) {
1379
+ event.preventDefault();
1380
+ setKbConnectFrom(null);
1381
+ }
1382
+ return;
1383
+ }
1384
+ if (event.key !== "Enter" && event.key !== " ") return;
1385
+ event.preventDefault();
1386
+ event.stopPropagation();
1387
+ activatePortByKeyboard();
1292
1388
  }
1293
1389
  }, dir))
1294
1390
  ]
1295
1391
  }, key);
1296
1392
  };
1393
+ const overlayOpen = kbOpen || manageOpen || reviewOpen && meeting?.review !== null;
1394
+ const renderToast = () => toast === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1395
+ className: RoundTableView_module_css_default.toast,
1396
+ style: overlayOpen ? {
1397
+ position: "fixed",
1398
+ zIndex: 1300
1399
+ } : void 0,
1400
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1401
+ className: toast.kind === "ok" ? RoundTableView_module_css_default.toastOk : RoundTableView_module_css_default.toastErr,
1402
+ children: toast.text
1403
+ })
1404
+ });
1297
1405
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1298
1406
  className: RoundTableView_module_css_default.root,
1299
1407
  children: [
@@ -1441,7 +1549,7 @@ function RoundTableView(props) {
1441
1549
  }),
1442
1550
  renderNode("captain", "DeepSeek · 主持", "captain"),
1443
1551
  renderNode("aggregator", "汇聚网关", "aggregator"),
1444
- meeting.nodes.filter((node) => node.status !== "removed").map((node) => renderNode(node.key, node.key, "node")),
1552
+ meeting.nodes.filter((node) => node.status !== "removed").map((node) => renderNode(node.key, node.key.startsWith("expert-") ? node.key.slice(7) : node.key, "node")),
1445
1553
  edgeRemoveDots.map((dot) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1446
1554
  type: "button",
1447
1555
  className: [RoundTableView_module_css_default.edgeRemove, hoverEdgeId === dot.id ? RoundTableView_module_css_default.edgeRemoveActive : ""].filter(Boolean).join(" "),
@@ -1507,13 +1615,7 @@ function RoundTableView(props) {
1507
1615
  })
1508
1616
  ]
1509
1617
  }),
1510
- toast !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1511
- className: RoundTableView_module_css_default.toast,
1512
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1513
- className: toast.kind === "ok" ? RoundTableView_module_css_default.toastOk : RoundTableView_module_css_default.toastErr,
1514
- children: toast.text
1515
- })
1516
- }) : null
1618
+ overlayOpen ? null : renderToast()
1517
1619
  ]
1518
1620
  }),
1519
1621
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
@@ -2442,6 +2544,7 @@ function RoundTableView(props) {
2442
2544
  })
2443
2545
  ]
2444
2546
  }) : null,
2547
+ overlayOpen ? renderToast() : null,
2445
2548
  fetchFailed === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2446
2549
  className: RoundTableView_module_css_default.fetchFailed,
2447
2550
  children: translate("fetchFailed")
@@ -2450,9 +2553,77 @@ function RoundTableView(props) {
2450
2553
  });
2451
2554
  }
2452
2555
 
2556
+ //#endregion
2557
+ //#region src/client/icons.tsx
2558
+ const SHAPES = {
2559
+ panel: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
2560
+ x: "2.2",
2561
+ y: "3.2",
2562
+ width: "11.6",
2563
+ height: "9.6",
2564
+ rx: "2"
2565
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M6.2 3.2v9.6" })] }),
2566
+ skill: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M9.2 2 4.6 8.8h3L6.8 14l4.6-6.8h-3L9.2 2Z" }),
2567
+ role: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2568
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
2569
+ x: "2.2",
2570
+ y: "3.2",
2571
+ width: "11.6",
2572
+ height: "9.6",
2573
+ rx: "2"
2574
+ }),
2575
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
2576
+ cx: "6",
2577
+ cy: "7.2",
2578
+ r: "1.4"
2579
+ }),
2580
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8.6 6.4h3.2M8.6 8.4h2.4" })
2581
+ ] }),
2582
+ squad: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2583
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
2584
+ cx: "6",
2585
+ cy: "6",
2586
+ r: "2"
2587
+ }),
2588
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2.6 13c.3-2.1 1.7-3.2 3.4-3.2s3.1 1.1 3.4 3.2" }),
2589
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
2590
+ cx: "11.4",
2591
+ cy: "6.6",
2592
+ r: "1.5"
2593
+ }),
2594
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M11.4 9.4c1.5 0 2.5 1 2.8 2.6" })
2595
+ ] }),
2596
+ gauge: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2597
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2.2 11.5a5.8 5.8 0 1 1 11.6 0" }),
2598
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8 11.5 10.8 6.5" }),
2599
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
2600
+ cx: "8",
2601
+ cy: "11.5",
2602
+ r: "0.9"
2603
+ })
2604
+ ] }),
2605
+ message: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M3 3.4h10a1.6 1.6 0 0 1 1.6 1.6v5.2A1.6 1.6 0 0 1 13 11.8H8.4L5 14v-2.2H3A1.6 1.6 0 0 1 1.4 10.2V5A1.6 1.6 0 0 1 3 3.4Z" })
2606
+ };
2607
+ /** 渲染一个 16×16 线性图标,颜色跟随 `currentColor`。 */
2608
+ function InlineIcon({ name, size = 14 }) {
2609
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2610
+ width: size,
2611
+ height: size,
2612
+ viewBox: "0 0 16 16",
2613
+ fill: "none",
2614
+ stroke: "currentColor",
2615
+ strokeWidth: "1.4",
2616
+ strokeLinecap: "round",
2617
+ strokeLinejoin: "round",
2618
+ "aria-hidden": "true",
2619
+ focusable: "false",
2620
+ children: SHAPES[name]
2621
+ });
2622
+ }
2623
+
2453
2624
  //#endregion
2454
2625
  //#region \0dsh-css:F:\AI\DSH\1changyong\dsh-plugin-roundtable\src\client\RoundTableSettings.module.css.mjs
2455
- const css = ".dx5ni8t_root {\n display: flex;\n flex-direction: column;\n gap: 16px;\n max-width: 560px;\n padding: 8px 0;\n}\n\n.dx5ni8t_field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.dx5ni8t_label {\n font-size: 13px;\n font-weight: 600;\n color: rgba(0, 0, 0, 0.8);\n}\n\n.dx5ni8t_select,\n.dx5ni8t_input {\n font-size: 13px;\n padding: 6px 10px;\n border-radius: 8px;\n border: 1px solid rgba(128, 128, 128, 0.4);\n background: #ffffff;\n color: rgba(0, 0, 0, 0.85);\n max-width: 260px;\n}\n\n.dx5ni8t_hint {\n font-size: 12px;\n color: rgba(0, 0, 0, 0.5);\n line-height: 1.6;\n}\n\n/* Minimal toggle switch. */\n.dx5ni8t_switchRow {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n cursor: pointer;\n width: fit-content;\n}\n\n.dx5ni8t_switchInput {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n}\n\n.dx5ni8t_switchTrack {\n position: relative;\n width: 36px;\n height: 20px;\n border-radius: 999px;\n background: rgba(128, 128, 128, 0.35);\n transition: background 140ms ease;\n flex: none;\n}\n\n.dx5ni8t_switchTrack::after {\n content: '';\n position: absolute;\n top: 2px;\n left: 2px;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);\n transition: transform 140ms ease;\n}\n\n.dx5ni8t_switchInput:checked + .dx5ni8t_switchTrack {\n background: #4d6bfe;\n}\n\n.dx5ni8t_switchInput:checked + .dx5ni8t_switchTrack::after {\n transform: translateX(16px);\n}\n\n.dx5ni8t_switchLabel {\n font-size: 12px;\n color: rgba(0, 0, 0, 0.6);\n}\n\n.dx5ni8t_warning {\n font-size: 12px;\n color: #a06b00;\n padding: 8px 10px;\n border-radius: 8px;\n background: rgba(214, 158, 46, 0.12);\n border: 1px solid rgba(214, 158, 46, 0.4);\n line-height: 1.6;\n}\n\n.dx5ni8t_actions {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n.dx5ni8t_saveButton {\n font-size: 13px;\n padding: 7px 18px;\n border-radius: 8px;\n border: none;\n background: #4d6bfe;\n color: #ffffff;\n cursor: pointer;\n}\n\n.dx5ni8t_saveButton:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n.dx5ni8t_saved {\n font-size: 12px;\n color: #10a37f;\n}\n\n.dx5ni8t_failed {\n font-size: 12px;\n color: #c62828;\n}\n\n.dx5ni8t_note {\n font-size: 12px;\n color: rgba(0, 0, 0, 0.5);\n}\n\n.dx5ni8t_sectionDivider {\n height: 1px;\n background: rgba(128, 128, 128, 0.2);\n margin: 2px 0;\n}\n\n.dx5ni8t_sectionTitle {\n font-size: 13px;\n font-weight: 700;\n color: rgba(0, 0, 0, 0.75);\n}\n\n/* ---------- R3:右栏面板显示(点圆圈决定显示/隐藏) ---------- */\n\n.dx5ni8t_panelToggles {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n max-width: 100%;\n}\n\n.dx5ni8t_panelToggleOn,\n.dx5ni8t_panelToggleOff {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n padding: 5px 10px 5px 8px;\n border-radius: 999px;\n cursor: pointer;\n transition: background 140ms ease, border-color 140ms ease, color 140ms ease;\n}\n\n/* 点亮 = 显示;暗掉 = 隐藏。 */\n.dx5ni8t_panelToggleOn {\n border: 1px solid rgba(77, 107, 254, 0.45);\n background: rgba(77, 107, 254, 0.10);\n color: rgba(0, 0, 0, 0.8);\n}\n\n.dx5ni8t_panelToggleOff {\n border: 1px solid rgba(128, 128, 128, 0.3);\n background: transparent;\n color: rgba(0, 0, 0, 0.38);\n}\n\n.dx5ni8t_panelDot {\n width: 9px;\n height: 9px;\n border-radius: 50%;\n flex: none;\n border: 1px solid transparent;\n}\n\n.dx5ni8t_panelToggleOn .dx5ni8t_panelDot {\n background: #4d6bfe;\n}\n\n.dx5ni8t_panelToggleOff .dx5ni8t_panelDot {\n background: transparent;\n border-color: rgba(128, 128, 128, 0.6);\n}\n\n.dx5ni8t_panelToggleLabel {\n line-height: 1;\n}\n\n.dx5ni8t_feedbackNote {\n font-size: 12px;\n color: rgba(0, 0, 0, 0.55);\n background: rgba(77, 107, 254, 0.06);\n border-left: 3px solid rgba(77, 107, 254, 0.3);\n padding: 6px 10px;\n border-radius: 0 8px 8px 0;\n line-height: 1.6;\n}\n\n.dx5ni8t_feedbackList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n max-height: 260px;\n overflow-y: auto;\n}\n\n.dx5ni8t_feedbackItem {\n font-size: 12px;\n color: rgba(0, 0, 0, 0.7);\n background: rgba(0, 0, 0, 0.03);\n border: 1px solid rgba(128, 128, 128, 0.2);\n border-radius: 8px;\n padding: 8px 10px;\n line-height: 1.5;\n}\n\n.dx5ni8t_feedbackMeta {\n font-size: 11px;\n color: rgba(0, 0, 0, 0.45);\n}\n\n.dx5ni8t_feedbackNoteText {\n margin-top: 4px;\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.dx5ni8t_feedbackClearBtn {\n font-size: 12px;\n padding: 4px 12px;\n border-radius: 8px;\n border: 1px solid rgba(198, 40, 40, 0.4);\n background: transparent;\n color: #c62828;\n cursor: pointer;\n align-self: flex-start;\n}\n\n.dx5ni8t_feedbackClearBtn:hover {\n background: rgba(198, 40, 40, 0.06);\n}\n\n/* ---------- B3:用户自建角色预设(扁平、克制,与 panelToggles 同观感) ---------- */\n\n.dx5ni8t_presetList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n max-height: 260px;\n overflow-y: auto;\n}\n\n.dx5ni8t_presetItem {\n display: flex;\n align-items: center;\n gap: 10px;\n font-size: 12px;\n color: rgba(0, 0, 0, 0.7);\n background: rgba(0, 0, 0, 0.03);\n border: 1px solid rgba(128, 128, 128, 0.2);\n border-radius: 8px;\n padding: 8px 10px;\n}\n\n.dx5ni8t_presetInfo {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n flex: 1;\n}\n\n.dx5ni8t_presetName {\n font-size: 13px;\n font-weight: 600;\n color: rgba(0, 0, 0, 0.8);\n}\n\n.dx5ni8t_presetMeta {\n font-size: 11px;\n color: rgba(0, 0, 0, 0.5);\n line-height: 1.5;\n word-break: break-word;\n}\n\n.dx5ni8t_presetRoute {\n font-size: 11px;\n color: rgba(0, 0, 0, 0.45);\n}\n\n.dx5ni8t_presetActions {\n display: flex;\n gap: 6px;\n flex: none;\n}\n\n.dx5ni8t_presetBtn {\n font-size: 12px;\n padding: 4px 10px;\n border-radius: 8px;\n border: 1px solid rgba(128, 128, 128, 0.35);\n background: transparent;\n color: rgba(0, 0, 0, 0.7);\n cursor: pointer;\n}\n\n.dx5ni8t_presetBtn:hover {\n background: rgba(0, 0, 0, 0.04);\n}\n\n.dx5ni8t_presetDeleteBtn {\n border-color: rgba(198, 40, 40, 0.35);\n color: #c62828;\n}\n\n.dx5ni8t_presetDeleteBtn:hover {\n background: rgba(198, 40, 40, 0.06);\n}\n\n.dx5ni8t_presetForm {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 10px;\n border-radius: 8px;\n border: 1px dashed rgba(128, 128, 128, 0.35);\n background: rgba(0, 0, 0, 0.015);\n}\n\n.dx5ni8t_presetFormRow {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.dx5ni8t_presetModelRow {\n display: flex;\n gap: 8px;\n}\n\n.dx5ni8t_presetSelect {\n flex: 1;\n min-width: 0;\n font-size: 13px;\n padding: 6px 10px;\n border-radius: 8px;\n border: 1px solid rgba(128, 128, 128, 0.4);\n background: #ffffff;\n color: rgba(0, 0, 0, 0.85);\n}\n\n.dx5ni8t_presetFormActions {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.dx5ni8t_presetAddBtn {\n font-size: 13px;\n padding: 7px 18px;\n border-radius: 8px;\n border: none;\n background: #4d6bfe;\n color: #ffffff;\n cursor: pointer;\n align-self: flex-start;\n}\n\n.dx5ni8t_presetCancelBtn {\n font-size: 12px;\n padding: 4px 10px;\n border-radius: 8px;\n border: 1px solid rgba(128, 128, 128, 0.35);\n background: transparent;\n color: rgba(0, 0, 0, 0.6);\n cursor: pointer;\n}\n\n.dx5ni8t_presetCancelBtn:hover {\n background: rgba(0, 0, 0, 0.04);\n}\n\n/* ---------- B3+:阵容预设的成员行 ----------\n 一行 = 专家 key + 角色 + provider + model + 移除。窄屏折行,避免把\n 设置页撑出横向滚动条。 */\n\n.dx5ni8t_squadMemberRow {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n align-items: center;\n}\n\n.dx5ni8t_squadMemberRow .dx5ni8t_presetSelect {\n flex: 1 1 120px;\n min-width: 0;\n}\n";
2626
+ const css = "/* ---- 本地设计令牌(与针锋设置页同值,视觉重量刻意更低) ----\n *\n * 圆桌的设置页**不套卡片**:它的主界面是拓扑画布 + 右侧多面板,本来信息密度就高,\n * 设置页再加一层带边框的卡片,两个插件并排时会有明显割裂感。这里只共享\n * 字号 / 圆角 / 间距 / 控件规格,分组靠「细线 + 标题」表达。\n */\n.dx5ni8t_root {\n --rt-fs-11: 11px;\n --rt-fs-12: 12px;\n --rt-fs-13: 13px;\n --rt-fs-14: 14px;\n --rt-radius-s: 6px;\n --rt-radius-m: 8px;\n --rt-radius-l: 12px;\n --rt-space-1: 4px;\n --rt-space-2: 8px;\n --rt-space-3: 12px;\n --rt-space-4: 16px;\n --rt-control-h: 32px;\n --rt-border: var(--dsw-alias-border-l1);\n --rt-border-strong: var(--dsw-alias-border-l2);\n --rt-bg-soft: var(--dsw-alias-bg-layer-2);\n --rt-fg: var(--dsw-alias-label-primary);\n --rt-fg-2: var(--dsw-alias-label-secondary);\n --rt-brand: var(--dsw-alias-brand-primary);\n --rt-on-brand: var(--dsw-alias-bg-base);\n\n display: flex;\n flex-direction: column;\n gap: var(--rt-space-3);\n max-width: 620px;\n padding: 8px 0;\n color: var(--rt-fg);\n font-size: var(--rt-fs-13);\n line-height: 1.6;\n}\n\n.dx5ni8t_field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.dx5ni8t_label {\n font-size: var(--rt-fs-13);\n font-weight: 600;\n color: var(--rt-fg);\n}\n\n.dx5ni8t_select,\n.dx5ni8t_input {\n box-sizing: border-box;\n min-height: var(--rt-control-h);\n width: 100%;\n max-width: 320px;\n font-size: var(--rt-fs-13);\n padding: 6px 10px;\n border-radius: var(--rt-radius-m);\n border: 1px solid var(--rt-border-strong);\n background: var(--rt-bg-soft);\n color: var(--rt-fg);\n}\n\n.dx5ni8t_hint {\n font-size: var(--rt-fs-12);\n color: var(--rt-fg-2);\n line-height: 1.7;\n}\n\n/* Minimal toggle switch. */\n.dx5ni8t_switchRow {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n cursor: pointer;\n width: fit-content;\n}\n\n.dx5ni8t_switchInput {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n}\n\n.dx5ni8t_switchTrack {\n position: relative;\n width: 36px;\n height: 20px;\n border-radius: 999px;\n background: var(--dsw-alias-border-l2);\n transition: background 140ms ease;\n flex: none;\n}\n\n.dx5ni8t_switchTrack::after {\n content: '';\n position: absolute;\n top: 2px;\n left: 2px;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n /* 保留 #ffffff:滑块是「实底上的前景」(品牌蓝轨道 / 弱灰轨道),13 个令牌里没有\n on-brand 前景色;深色主题下白滑块在两种轨道上对比都是对的。\n 保留 rgba(0,0,0,0.25):投影没有对应令牌,换成任何语义色都会渲染错。 */\n background: #ffffff;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);\n transition: transform 140ms ease;\n}\n\n.dx5ni8t_switchInput:checked + .dx5ni8t_switchTrack {\n background: var(--dsw-alias-brand-primary);\n}\n\n.dx5ni8t_switchInput:checked + .dx5ni8t_switchTrack::after {\n transform: translateX(16px);\n}\n\n.dx5ni8t_switchLabel {\n font-size: var(--rt-fs-12);\n color: var(--rt-fg-2);\n}\n\n.dx5ni8t_warning {\n font-size: 12px;\n color: var(--dsw-alias-state-warn-primary);\n padding: 8px 10px;\n border-radius: 8px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 12%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 40%, transparent);\n line-height: 1.6;\n}\n\n.dx5ni8t_actions {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n.dx5ni8t_saveButton {\n box-sizing: border-box;\n min-height: var(--rt-control-h);\n font-size: var(--rt-fs-13);\n font-weight: 600;\n padding: 6px 16px;\n border-radius: var(--rt-radius-m);\n border: none;\n background: var(--rt-brand);\n color: var(--rt-on-brand);\n cursor: pointer;\n}\n\n.dx5ni8t_saveButton:hover:not(:disabled) {\n filter: brightness(1.06);\n}\n\n.dx5ni8t_saveButton:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n.dx5ni8t_saved {\n font-size: var(--rt-fs-12);\n color: var(--dsw-alias-state-success-primary);\n}\n\n.dx5ni8t_failed {\n font-size: var(--rt-fs-12);\n color: var(--dsw-alias-state-error-primary);\n}\n\n.dx5ni8t_note {\n font-size: var(--rt-fs-12);\n color: var(--rt-fg-2);\n}\n\n/* 分组:一条极淡的细线 + 一个紧贴其下的组标题。\n *\n * 之前是「1px 线 + 同样的 16px 空隙」,线和标题之间没有关系,读起来像随机分隔;\n * 现在让线成为标题的\"上边界\",标题到内容收紧到 8px,分组因此可辨。 */\n.dx5ni8t_sectionDivider {\n height: 1px;\n background: var(--rt-border);\n margin: var(--rt-space-3) 0 0;\n}\n\n.dx5ni8t_sectionTitle {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: var(--rt-fs-13);\n font-weight: 650;\n letter-spacing: 0.02em;\n color: var(--rt-fg);\n}\n\n/* 图标降一档:它是\"目录\"记号,不和文字抢。 */\n.dx5ni8t_sectionTitle svg {\n flex: none;\n color: var(--rt-fg-2);\n}\n\n/* 组标题与它下面的第一个控件贴紧,避免标题\"漂\"在两段空隙中间。 */\n.dx5ni8t_sectionTitle + .dx5ni8t_field,\n.dx5ni8t_sectionTitle + .dx5ni8t_panelToggles,\n.dx5ni8t_sectionTitle + .dx5ni8t_switchRow,\n.dx5ni8t_sectionTitle + .dx5ni8t_feedbackList,\n.dx5ni8t_sectionTitle + .dx5ni8t_presetList {\n margin-top: calc(var(--rt-space-1) * -1);\n}\n\n/* ---------- R3:右栏面板显示(点圆圈决定显示/隐藏) ---------- */\n\n/* 面板开关排成等宽网格:七项用 flex+wrap 时最后一行只剩一个,看起来像漏排。\n * 网格让每一行的起点对齐,行数随宽度自然变化。 */\n.dx5ni8t_panelToggles {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(136px, 1fr));\n gap: var(--rt-space-2);\n max-width: 100%;\n}\n\n.dx5ni8t_panelToggleOn,\n.dx5ni8t_panelToggleOff {\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n min-height: 30px;\n font-size: var(--rt-fs-12);\n padding: 4px 10px 4px 8px;\n border-radius: 999px;\n cursor: pointer;\n transition: background 140ms ease, border-color 140ms ease, color 140ms ease;\n}\n\n/* 点亮 = 显示;暗掉 = 隐藏。 */\n.dx5ni8t_panelToggleOn {\n border: 1px solid color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, transparent);\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 10%, transparent);\n color: var(--dsw-alias-label-primary);\n}\n\n.dx5ni8t_panelToggleOff {\n border: 1px solid var(--dsw-alias-border-l2);\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n}\n\n.dx5ni8t_panelDot {\n width: 9px;\n height: 9px;\n border-radius: 50%;\n flex: none;\n border: 1px solid transparent;\n}\n\n.dx5ni8t_panelToggleOn .dx5ni8t_panelDot {\n background: var(--dsw-alias-brand-primary);\n}\n\n.dx5ni8t_panelToggleOff .dx5ni8t_panelDot {\n background: transparent;\n border-color: var(--dsw-alias-border-l1);\n}\n\n.dx5ni8t_panelToggleLabel {\n line-height: 1;\n}\n\n.dx5ni8t_feedbackNote {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 6%, transparent);\n border-left: 3px solid color-mix(in srgb, var(--dsw-alias-brand-primary) 30%, transparent);\n padding: 6px 10px;\n border-radius: 0 8px 8px 0;\n line-height: 1.6;\n}\n\n.dx5ni8t_feedbackList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n max-height: 260px;\n overflow-y: auto;\n}\n\n.dx5ni8t_feedbackItem {\n font-size: var(--rt-fs-12);\n color: var(--rt-fg-2);\n background: var(--rt-bg-soft);\n border: 1px solid transparent;\n border-radius: var(--rt-radius-m);\n padding: 8px 10px;\n line-height: 1.5;\n}\n\n.dx5ni8t_feedbackMeta {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.dx5ni8t_feedbackNoteText {\n margin-top: 4px;\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.dx5ni8t_feedbackClearBtn {\n box-sizing: border-box;\n min-height: 28px;\n font-size: var(--rt-fs-12);\n padding: 3px 12px;\n border-radius: var(--rt-radius-s);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 40%, transparent);\n background: transparent;\n color: var(--dsw-alias-state-error-primary);\n cursor: pointer;\n align-self: flex-start;\n}\n\n.dx5ni8t_feedbackClearBtn:hover {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 6%, transparent);\n}\n\n/* ---------- B3:用户自建角色预设(扁平、克制,与 panelToggles 同观感) ---------- */\n\n.dx5ni8t_presetList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n max-height: 260px;\n overflow-y: auto;\n}\n\n.dx5ni8t_presetItem {\n display: flex;\n align-items: center;\n gap: 10px;\n font-size: var(--rt-fs-12);\n color: var(--rt-fg-2);\n background: var(--rt-bg-soft);\n border: 1px solid transparent;\n border-radius: var(--rt-radius-m);\n padding: 8px 10px;\n}\n\n.dx5ni8t_presetInfo {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n flex: 1;\n}\n\n.dx5ni8t_presetName {\n font-size: var(--rt-fs-13);\n font-weight: 600;\n color: var(--rt-fg);\n}\n\n.dx5ni8t_presetMeta {\n font-size: var(--rt-fs-11);\n color: var(--rt-fg-2);\n line-height: 1.5;\n word-break: break-word;\n}\n\n.dx5ni8t_presetRoute {\n font-size: var(--rt-fs-11);\n color: var(--rt-fg-2);\n}\n\n.dx5ni8t_presetActions {\n display: flex;\n gap: 6px;\n flex: none;\n}\n\n.dx5ni8t_presetBtn {\n box-sizing: border-box;\n min-height: 28px;\n font-size: var(--rt-fs-12);\n padding: 3px 10px;\n border-radius: var(--rt-radius-s);\n border: 1px solid var(--rt-border-strong);\n background: transparent;\n color: var(--rt-fg-2);\n cursor: pointer;\n}\n\n.dx5ni8t_presetBtn:hover {\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.dx5ni8t_presetDeleteBtn {\n border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary) 35%, transparent);\n color: var(--dsw-alias-state-error-primary);\n}\n\n.dx5ni8t_presetDeleteBtn:hover {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 6%, transparent);\n}\n\n.dx5ni8t_presetForm {\n display: flex;\n flex-direction: column;\n gap: var(--rt-space-2);\n padding: 12px;\n border-radius: var(--rt-radius-l);\n border: 1px solid var(--rt-border);\n background: var(--rt-bg-soft);\n}\n\n.dx5ni8t_presetFormRow {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.dx5ni8t_presetModelRow {\n display: flex;\n gap: 8px;\n}\n\n.dx5ni8t_presetSelect {\n box-sizing: border-box;\n flex: 1;\n min-width: 0;\n min-height: var(--rt-control-h);\n font-size: var(--rt-fs-13);\n padding: 6px 10px;\n border-radius: var(--rt-radius-m);\n border: 1px solid var(--rt-border-strong);\n background: var(--rt-bg-soft);\n color: var(--rt-fg);\n}\n\n.dx5ni8t_presetFormActions {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.dx5ni8t_presetAddBtn {\n box-sizing: border-box;\n min-height: var(--rt-control-h);\n font-size: var(--rt-fs-13);\n font-weight: 600;\n padding: 6px 16px;\n border-radius: var(--rt-radius-m);\n border: none;\n background: var(--rt-brand);\n color: var(--rt-on-brand);\n cursor: pointer;\n align-self: flex-start;\n}\n\n.dx5ni8t_presetCancelBtn {\n box-sizing: border-box;\n min-height: 28px;\n font-size: var(--rt-fs-12);\n padding: 3px 12px;\n border-radius: var(--rt-radius-s);\n border: 1px solid var(--rt-border-strong);\n background: transparent;\n color: var(--rt-fg-2);\n cursor: pointer;\n}\n\n.dx5ni8t_presetCancelBtn:hover {\n background: var(--dsw-alias-bg-layer-2);\n}\n\n/* ---------- B3+:阵容预设的成员行 ----------\n 一行 = 专家 key + 角色 + provider + model + 移除。窄屏折行,避免把\n 设置页撑出横向滚动条。 */\n\n.dx5ni8t_squadMemberRow {\n display: flex;\n flex-wrap: wrap;\n gap: var(--rt-space-2);\n align-items: center;\n}\n\n.dx5ni8t_squadMemberRow .dx5ni8t_presetSelect {\n flex: 1 1 140px;\n min-width: 0;\n}\n";
2456
2627
  const classMap = {
2457
2628
  "root": "dx5ni8t_root",
2458
2629
  "field": "dx5ni8t_field",
@@ -2473,17 +2644,17 @@ const classMap = {
2473
2644
  "sectionDivider": "dx5ni8t_sectionDivider",
2474
2645
  "sectionTitle": "dx5ni8t_sectionTitle",
2475
2646
  "panelToggles": "dx5ni8t_panelToggles",
2647
+ "feedbackList": "dx5ni8t_feedbackList",
2648
+ "presetList": "dx5ni8t_presetList",
2476
2649
  "panelToggleOn": "dx5ni8t_panelToggleOn",
2477
2650
  "panelToggleOff": "dx5ni8t_panelToggleOff",
2478
2651
  "panelDot": "dx5ni8t_panelDot",
2479
2652
  "panelToggleLabel": "dx5ni8t_panelToggleLabel",
2480
2653
  "feedbackNote": "dx5ni8t_feedbackNote",
2481
- "feedbackList": "dx5ni8t_feedbackList",
2482
2654
  "feedbackItem": "dx5ni8t_feedbackItem",
2483
2655
  "feedbackMeta": "dx5ni8t_feedbackMeta",
2484
2656
  "feedbackNoteText": "dx5ni8t_feedbackNoteText",
2485
2657
  "feedbackClearBtn": "dx5ni8t_feedbackClearBtn",
2486
- "presetList": "dx5ni8t_presetList",
2487
2658
  "presetItem": "dx5ni8t_presetItem",
2488
2659
  "presetInfo": "dx5ni8t_presetInfo",
2489
2660
  "presetName": "dx5ni8t_presetName",
@@ -2982,9 +3153,9 @@ function RoundTableSettings(props) {
2982
3153
  ]
2983
3154
  }),
2984
3155
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: RoundTableSettings_module_css_default.sectionDivider }),
2985
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3156
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2986
3157
  className: RoundTableSettings_module_css_default.sectionTitle,
2987
- children: t("settingsPanelsTitle")
3158
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(InlineIcon, { name: "panel" }), t("settingsPanelsTitle")]
2988
3159
  }),
2989
3160
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2990
3161
  className: RoundTableSettings_module_css_default.field,
@@ -3046,9 +3217,9 @@ function RoundTableSettings(props) {
3046
3217
  ]
3047
3218
  }),
3048
3219
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: RoundTableSettings_module_css_default.sectionDivider }),
3049
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3220
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3050
3221
  className: RoundTableSettings_module_css_default.sectionTitle,
3051
- children: t("settingsSkillTitle")
3222
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(InlineIcon, { name: "skill" }), t("settingsSkillTitle")]
3052
3223
  }),
3053
3224
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3054
3225
  className: RoundTableSettings_module_css_default.field,
@@ -3076,9 +3247,9 @@ function RoundTableSettings(props) {
3076
3247
  ]
3077
3248
  }),
3078
3249
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: RoundTableSettings_module_css_default.sectionDivider }),
3079
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3250
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3080
3251
  className: RoundTableSettings_module_css_default.sectionTitle,
3081
- children: t("settingsPresetsTitle")
3252
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(InlineIcon, { name: "role" }), t("settingsPresetsTitle")]
3082
3253
  }),
3083
3254
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3084
3255
  className: RoundTableSettings_module_css_default.field,
@@ -3239,9 +3410,9 @@ function RoundTableSettings(props) {
3239
3410
  ]
3240
3411
  }),
3241
3412
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: RoundTableSettings_module_css_default.sectionDivider }),
3242
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3413
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3243
3414
  className: RoundTableSettings_module_css_default.sectionTitle,
3244
- children: t("settingsSquadTitle")
3415
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(InlineIcon, { name: "squad" }), t("settingsSquadTitle")]
3245
3416
  }),
3246
3417
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3247
3418
  className: RoundTableSettings_module_css_default.field,
@@ -3416,9 +3587,9 @@ function RoundTableSettings(props) {
3416
3587
  ]
3417
3588
  }),
3418
3589
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: RoundTableSettings_module_css_default.sectionDivider }),
3419
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3590
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3420
3591
  className: RoundTableSettings_module_css_default.sectionTitle,
3421
- children: t("settingsLimitsTitle")
3592
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(InlineIcon, { name: "gauge" }), t("settingsLimitsTitle")]
3422
3593
  }),
3423
3594
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3424
3595
  className: RoundTableSettings_module_css_default.field,
@@ -3466,9 +3637,9 @@ function RoundTableSettings(props) {
3466
3637
  ]
3467
3638
  }),
3468
3639
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: RoundTableSettings_module_css_default.sectionDivider }),
3469
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3640
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3470
3641
  className: RoundTableSettings_module_css_default.sectionTitle,
3471
- children: t("feedbackTitle")
3642
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(InlineIcon, { name: "message" }), t("feedbackTitle")]
3472
3643
  }),
3473
3644
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3474
3645
  className: RoundTableSettings_module_css_default.field,
@@ -3621,7 +3792,7 @@ function nodeLine(node) {
3621
3792
  //#endregion
3622
3793
  //#region src/client/tool-views.tsx
3623
3794
  const CARD = {
3624
- border: "1px solid rgba(128,128,128,0.35)",
3795
+ border: "1px solid var(--dsw-alias-border-l1)",
3625
3796
  borderRadius: 8,
3626
3797
  padding: "8px 10px",
3627
3798
  font: "12px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace",
@@ -3632,7 +3803,7 @@ const HEAD = {
3632
3803
  marginBottom: 4
3633
3804
  };
3634
3805
  const MUTED = { opacity: .75 };
3635
- const WARN = { color: "#e0a030" };
3806
+ const WARN = { color: "var(--dsw-alias-state-warn-primary)" };
3636
3807
  /** Card body for a parsed status payload. */
3637
3808
  function StatusCard({ data }) {
3638
3809
  const budget = data.budget ?? {};
package/lib/index.js CHANGED
@@ -89,7 +89,7 @@ async function evaluateKbDigests(entries) {
89
89
  * @module dsh-plugin-roundtable/version
90
90
  */
91
91
  /** 当前插件版本(必须与 package.json 的 version 逐字一致)。 */
92
- const PLUGIN_VERSION = "1.0.0-rc.2";
92
+ const PLUGIN_VERSION = "1.0.0-rc.4";
93
93
  /** 导出物头部使用的插件标识行。 */
94
94
  const PLUGIN_ID = `@y9931666/dsh-plugin-roundtable v${PLUGIN_VERSION}`;
95
95
  /** DeepSeek Harness 版本区间(导出头部展示用)。
@@ -0,0 +1,15 @@
1
+ /**
2
+ * 内联 SVG 图标(零依赖、零资源、随主题变色)。
3
+ *
4
+ * 与针锋 `icons.tsx` 同一套几何规格(16×16、1.4px 描边、currentColor),但按圆桌
5
+ * 自己的设置页需要收窄到六个图标 —— 两个插件各自独立打包,不跨包共享源码。
6
+ *
7
+ * @module dsh-plugin-roundtable/client/icons
8
+ */
9
+ /** 圆桌设置页用到的图标名。新增图标必须同时加进 {@link SHAPES}。 */
10
+ export type IconName = 'panel' | 'skill' | 'role' | 'squad' | 'gauge' | 'message';
11
+ /** 渲染一个 16×16 线性图标,颜色跟随 `currentColor`。 */
12
+ export declare function InlineIcon({ name, size }: {
13
+ name: IconName;
14
+ size?: number;
15
+ }): JSX.Element;
@@ -9,9 +9,9 @@
9
9
  * @module dsh-plugin-roundtable/version
10
10
  */
11
11
  /** 当前插件版本(必须与 package.json 的 version 逐字一致)。 */
12
- export declare const PLUGIN_VERSION = "1.0.0-rc.2";
12
+ export declare const PLUGIN_VERSION = "1.0.0-rc.4";
13
13
  /** 导出物头部使用的插件标识行。 */
14
- export declare const PLUGIN_ID = "@y9931666/dsh-plugin-roundtable v1.0.0-rc.2";
14
+ export declare const PLUGIN_ID = "@y9931666/dsh-plugin-roundtable v1.0.0-rc.4";
15
15
  /** DeepSeek Harness 版本区间(导出头部展示用)。
16
16
  *
17
17
  * 必须与 `compatibility.json` 的 `recommendedHost` 一致 —— 由
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@y9931666/dsh-plugin-roundtable",
3
- "version": "1.0.0-rc.2",
3
+ "version": "1.0.0-rc.4",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "registry": "https://registry.npmjs.org/"
@@ -0,0 +1,127 @@
1
+ # RoundTable v1.0.0-rc.3
2
+
3
+ **视觉与交互修复版:让界面在深色主题下成立,并把四个交互/性能缺陷修掉。**
4
+
5
+ 这一版的全部改动来自一次**对抗式评审**(用针锋 Crossfire 对圆桌的视觉设计做红队审查,
6
+ 红队只读源码取证,逐条给出文件与行号)。没有新功能。
7
+
8
+ > **发行渠道:npm + GitHub Release。** 包名 `@y9931666/dsh-plugin-roundtable`
9
+ > (作者的 npm 账户名;早期文档里的 `@huanlin/...` 属于另一个用户,不可用)。
10
+ > 本版是**预发布**(`1.0.0-rc.3`),进 npm 的 `next` 渠道,安装要带标签:
11
+ > `dsh plugin --profile web add @y9931666/dsh-plugin-roundtable@next`
12
+ > (源码安装:`git clone https://github.com/9931666/dsh-plugin-roundtable` → `pnpm install` → `pnpm build` → `dsh plugin --profile web add .`)
13
+ >
14
+ > 宿主基线:**DeepSeek Harness 0.2.0-rc.2**
15
+
16
+ ---
17
+
18
+ ## 0. 一句话
19
+
20
+ 圆桌的浏览器半体此前**一个主题令牌都没用**:两个样式表里 `#ffffff` 底色与
21
+ `rgba(0,0,0,.4~.85)` 文字全部写死。**深色主题下前景与背景亮度差趋近于 0 —— 整页看起来"什么都没有"。**
22
+ 本版把 315 处硬编码颜色换成宿主的 13 个 alias 令牌,并修掉 4 个交互/性能缺陷。
23
+
24
+ ---
25
+
26
+ ## 1. 深色主题整片不可读(最严重)
27
+
28
+ **症状**:把外观切到深色后,「圆桌会议」标签页的文字、面板、弹窗仍是浅色值。
29
+ 空状态(`空标题 + 空提示`)尤其明显:两个类都只用写死的近黑文字、自身不设背景,
30
+ 落点是宿主那张**随主题翻转**的会话底色。
31
+
32
+ **修法**:全部换成宿主 `@deepseek-ai/dsh-client-ui-theme` 的 alias 令牌 ——
33
+ 底色 `bg-base/bg-layer-1/bg-layer-2/bg-overlay`、文字 `label-primary/label-secondary`、
34
+ 边框 `border-l1/border-l2`、状态 `state-{error,success,warn,idle}-primary`、
35
+ 品牌 `brand-primary`。透明变体统一用 `color-mix(in srgb, var(--token) N%, transparent)`
36
+ (宿主自身 CSS 就在用这个语法)。
37
+
38
+ **保留的例外**(有就地注释):第三方品牌 logo 色(`PROVIDER_BRAND`,商标色无法用令牌表达)、
39
+ 品牌色填充上的白字、主持人锚点的刻意深色底、红色圆点上的白色 ×。
40
+
41
+ ## 2. 蓝色强调曾映射到"单色令牌"
42
+
43
+ `--dsw-alias-brand-primary` 实测是**单色**(浅色 `#0f1115` / 深色 `#f9fafb`),
44
+ 宿主自己只用它 2 次 —— 它**不是强调色**。把它用在蓝色强调上,会让端口描边、
45
+ 拖拽线、消息脉冲、开关 accent 在浅色下变近黑、深色下变近白,**蓝色身份丢失**。
46
+
47
+ **修法**:32 处蓝色强调改用宿主真实的强调蓝
48
+ `--dsw-alias-brand-primary-new-colorprimary-new-color`
49
+ (浅色 `#4176e6` / 深色 `var(--dsw-static-deepseek-450)` = `#5686fe`,
50
+ 宿主自用 14 次,用法同构:图标色 / chip 底 / 徽标填充 / focus 指示条 / 时间线 rail)。
51
+
52
+ 顺带修掉三处因此暴露出来的自相矛盾:
53
+ - `.msgPulse`:蓝色辉光围着**近白**圆点 → 圆点与辉光统一为强调蓝;
54
+ - `.port:hover`:蓝色描边 + 悬停填**近白** → 与描边统一为强调蓝;
55
+ - `.manageAddBtn`:深色下白字压**近白**底 → 文字色改用 `--dsw-alias-label-primary-foreground`
56
+ (宿主 primitives 里"填充上的文字色":浅色 `#fff` / 深色 `#0f1115`)。
57
+
58
+ ## 3. 评审弹窗里的操作**看不到任何反馈**
59
+
60
+ **症状**:在弹窗里点「支持(认定为缺陷)」,或空理由点「确认驳回」,界面毫无回执。
61
+
62
+ **根因**:所有表态反馈都只走 `setToast`,而 toast 的唯一渲染点在 `.canvas` 内、
63
+ `z-index: 6`;弹窗遮罩 `.manageOverlay` 是 `position: fixed; inset: 0; z-index: 1200` ——
64
+ **toast 被压在整屏 35% 遮罩之下**,且 860px 居中的弹窗卡片正好盖住画布顶部同一区域。
65
+ 三条专为弹窗写的文案在界面上**不可达**。
66
+
67
+ **修法**:按"有没有弹窗"决定渲染层级 —— 无弹窗时仍留在画布内(观感零变化),
68
+ 有弹窗时挂到 `.root` 直属层级并 `position: fixed; zIndex: 1300`。一处改动覆盖知识库/管理/评审三个弹窗。
69
+
70
+ ## 4. 拉线端口可聚焦,但键盘**不可能完成连线**
71
+
72
+ **症状**:每个节点有 4 个 11px 端口,带 `role="button"`、`tabIndex={0}`、
73
+ `aria-label="connect"`,CSS 还专门写了 `.port:focus-visible` —— 但唯一入口是 `onMouseDown`,
74
+ 延续依赖 window 上的 `mousemove/mouseup`。Tab 能停在 20 个端口上,按 Enter/空格**无任何反应**。
75
+
76
+ **修法**:实现真键盘闭环 —— Enter/空格选中起点(toast 提示)→ 在另一节点端口 Enter 落点;
77
+ 同节点再 Enter 或 Esc 取消;补 `aria-pressed`。落点判定与鼠标**共用同一个 `connectNodes()`**,
78
+ 不存在两套实现。
79
+
80
+ ## 5. 拉线时每个 `mousemove` 重渲染整棵树
81
+
82
+ **症状**:`mousemove` 直接 `setDrag`,无节流无合帧;effect 还把 `drag` 放进依赖数组,
83
+ 于是**每个鼠标事件**都拆除并重挂一对 window 监听器,并重建整棵子树(含边几何与时间格式化)。
84
+
85
+ **修法**:rAF 合帧 + 用 ref 持有最新坐标(mousemove 只同步写 ref,按帧 setState),
86
+ effect 依赖由 `drag` 改为布尔量。落点判定改用 ref 里的真实坐标,**不依赖上一帧渲染坐标**,
87
+ 避免"指针停下时线停在上一帧"。
88
+
89
+ ## 6. 边几何 O(E²)
90
+
91
+ **症状**:`renderEdge` 内对**每条边**重复扫描整个 `meeting.edges`,且与数据是否变化无关。
92
+
93
+ **修法**:一次 O(E) 建 `edgeFanTable`(出/入度 + 组内序号,序号语义与原实现逐字等价),
94
+ `renderEdge` 与 `edgeRemoveDots` 两处改查表。**几何结果与改前逐位一致**,行为零变化。
95
+
96
+ ## 7. 三态按钮的颜色语义与针锋插件相反
97
+
98
+ 圆桌里「支持(认定为缺陷)」是**红色系**、「驳回」是**绿色系**;
99
+ 而针锋 Crossfire 的约定相反(认定=绿、驳回=红)。同一用户在两个界面间切换时**读色等于反向读数**。
100
+
101
+ **修法**:按语义对调为「认定=success / 驳回=error」,并留注释说明"与针锋 Crossfire 约定一致"以防回归。
102
+
103
+ ## 8. 工具卡片里的写死琥珀色
104
+
105
+ `tool-views.tsx` 的 `WARN` 用了写死的 `#e0a030`,卡片边框也是硬编码灰色 ——
106
+ 同属深色主题隐患。已改为 `--dsw-alias-state-warn-primary` 与 `--dsw-alias-border-l1`。
107
+
108
+ ---
109
+
110
+ ## 验收与已知限制
111
+
112
+ - `npm.cmd run typecheck` → exit 0;`npm.cmd run test:inline` → **150 / 150 pass**。
113
+ - 两个样式表所用令牌经脚本校验:**全部在宿主白名单内,无自造令牌名**
114
+ (自造名在深色下会静默失效,是最隐蔽的失败方式)。
115
+ - **需要人眼确认**(代码无法验证主观观感):深色主题下的整体观感与对比度、
116
+ 拖拽合帧后的跟手度、弹窗回执是否确实浮在遮罩之上、键盘连线路径的手感。
117
+ - 未做:颜色之外的主题维度(字号/间距并非本次目标)。
118
+
119
+ ---
120
+
121
+ ## 安装
122
+
123
+ ```sh
124
+ dsh plugin --profile web add @y9931666/dsh-plugin-roundtable@next
125
+ ```
126
+
127
+ 装后刷新浏览器即可(本版只动浏览器半体);若宿主也升级了则按惯例重启。
@@ -0,0 +1,26 @@
1
+ # v1.0.0-rc.4 — 界面视觉优化(轻量)
2
+
3
+ 本版只改界面,不改会议协议、工具语义与数据格式。视觉重量刻意低于针锋的卡片化:
4
+ 圆桌的主界面是拓扑画布 + 多面板,设置页再加一层带边框的卡片会与针锋产生割裂感。
5
+
6
+ ## 设置页
7
+
8
+ - **轻分组**:保留「细线 + 组标题」结构,不套卡片;组标题成为细线的下边界,标题到内容收紧 4px。
9
+ - **控件统一**:输入 / 下拉 32px 高、8px 圆角、320 宽、次级背景;主按钮 32px、次按钮 28px。
10
+ - **面板开关**:从 flex 换行改为等宽网格,七项不再出现"最后一行只剩一个"。
11
+ - **列表项**:预设 / 反馈条目去边框,只留浅底(边框曾是视觉重量最大的来源)。
12
+ - **图标**:分组标题前加 16×16 内联 SVG 图标(`src/client/icons.tsx`),零依赖、随主题变色。
13
+
14
+ ## 拓扑视图
15
+
16
+ - **节点放大**:专家节点 68 → 84px(`NODE_RADIUS` 34 → 42,连线端点与环形最小半径同步);头像 30 → 36px。此前节点在 1900px 宽的画布里显得过小。
17
+ - **头像去灰边**:头像不再套一圈 `inset` 灰色圆环 —— 它与节点自身的边框叠成"框里还有框"。
18
+ - **主持块对比度**:DeepSeek 主持的黑块上,文字从 `label-secondary`(浅底次要文字色,黑底上读不出来)改为固定白色;块尺寸 96×88 → 108×96。
19
+ - **节点名**:显示时去掉专家共有的 `expert-` 前缀并允许两行(`expert-lifecycle` → `lifecycle`);完整 key 仍由悬停 `title` 提供。
20
+ - **连线**:专家间连线从最低对比的边框色改为次级文字色,线宽 1.6、圆端点,浅色底上不再"几乎看不见"。
21
+ - **右栏面板**:标题升为正文色 + 字距(此前与说明文字同级),内容内边距 8→10,面板间距 10→12。
22
+ - **预算进度条**:90×5px → 100×6px。
23
+
24
+ ## 构建说明
25
+
26
+ `tsdown` 的 clean 会清空 `lib/types`;声明文件需在正常环境用 `npm run build`(含 `tsc --emitDeclarationOnly`)生成。