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

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/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: 6px;\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: 90px;\n height: 5px;\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.dd3px29_edgeForward {\n stroke: var(--dsw-alias-border-l1);\n stroke-width: 1.5;\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: 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: 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: 96px;\n height: 88px;\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.dd3px29_captainNode .dd3px29_nodeLabel {\n color: var(--dsw-alias-label-secondary);\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: var(--dsw-alias-bg-layer-2);\n border: 1.5px dashed var(--dsw-alias-border-l2);\n}\n\n.dd3px29_nodeLabel {\n font-size: 11px;\n color: var(--dsw-alias-label-primary);\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 color-mix(in srgb, var(--dsw-alias-label-primary) 22%, transparent);\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: 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: 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: 6px 10px;\n border-bottom: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.dd3px29_panelTitle {\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-label-secondary);\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: 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: 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",
@@ -429,10 +429,16 @@ function arrowPoints(x, y, angle, size = 7) {
429
429
  };
430
430
  return `${tip.x},${tip.y} ${left.x},${left.y} ${right.x},${right.y}`;
431
431
  }
432
+ const ZERO_FAN = {
433
+ fromDegree: 0,
434
+ fromIndex: 0,
435
+ toDegree: 0,
436
+ toIndex: 0
437
+ };
432
438
  /** Quadratic-bezier geometry for one edge, shared by the renderer (which
433
439
  * draws the path) and the delete dot (which must sit on the curve's true
434
440
  * midpoint, i.e. the point at t=0.5 — not the control point). */
435
- function edgeCurveGeometry(edge, meeting, positions) {
441
+ function edgeCurveGeometry(edge, positions, fan) {
436
442
  const from = positions.get(edge.from);
437
443
  const to = positions.get(edge.to);
438
444
  if (from === void 0 || to === void 0) return null;
@@ -442,10 +448,7 @@ function edgeCurveGeometry(edge, meeting, positions) {
442
448
  const len = Math.max(1, Math.hypot(dx, dy));
443
449
  const ux = dx / len;
444
450
  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);
451
+ const { fromDegree, fromIndex, toDegree, toIndex } = fan;
449
452
  const hubFrom = edge.from === "captain" || edge.from === "aggregator";
450
453
  const hubTo = edge.to === "captain" || edge.to === "aggregator";
451
454
  const baseAngle = Math.atan2(uy, ux);
@@ -560,6 +563,8 @@ function RoundTableView(props) {
560
563
  const [fetchFailed, setFetchFailed] = (0, react.useState)(false);
561
564
  const [menu, setMenu] = (0, react.useState)(null);
562
565
  const [drag, setDrag] = (0, react.useState)(null);
566
+ const dragRef = (0, react.useRef)(null);
567
+ const [kbConnectFrom, setKbConnectFrom] = (0, react.useState)(null);
563
568
  const [toast, setToast] = (0, react.useState)(null);
564
569
  const [hoverEdgeId, setHoverEdgeId] = (0, react.useState)(null);
565
570
  const [showAll, setShowAll] = (0, react.useState)(true);
@@ -776,12 +781,37 @@ function RoundTableView(props) {
776
781
  }));
777
782
  };
778
783
  const positions = (0, react.useMemo)(() => meeting === void 0 ? /* @__PURE__ */ new Map() : layoutPositions(size, meeting), [meeting, size]);
784
+ const edgeFanTable = (0, react.useMemo)(() => {
785
+ const fans = /* @__PURE__ */ new Map();
786
+ if (meeting === void 0) return fans;
787
+ const outDegree = /* @__PURE__ */ new Map();
788
+ const inDegree = /* @__PURE__ */ new Map();
789
+ for (const edge of meeting.edges) {
790
+ outDegree.set(edge.from, (outDegree.get(edge.from) ?? 0) + 1);
791
+ inDegree.set(edge.to, (inDegree.get(edge.to) ?? 0) + 1);
792
+ }
793
+ const outCursor = /* @__PURE__ */ new Map();
794
+ const inCursor = /* @__PURE__ */ new Map();
795
+ for (const edge of meeting.edges) {
796
+ const fromIndex = outCursor.get(edge.from) ?? 0;
797
+ outCursor.set(edge.from, fromIndex + 1);
798
+ const toIndex = inCursor.get(edge.to) ?? 0;
799
+ inCursor.set(edge.to, toIndex + 1);
800
+ fans.set(edge.id, {
801
+ fromDegree: outDegree.get(edge.from) ?? 0,
802
+ fromIndex,
803
+ toDegree: inDegree.get(edge.to) ?? 0,
804
+ toIndex
805
+ });
806
+ }
807
+ return fans;
808
+ }, [meeting]);
779
809
  const edgeRemoveDots = (0, react.useMemo)(() => {
780
810
  if (meeting === void 0) return [];
781
811
  const dots = [];
782
812
  for (const edge of meeting.edges) {
783
813
  if (edge.id.startsWith("synthetic:")) continue;
784
- const geo = edgeCurveGeometry(edge, meeting, positions);
814
+ const geo = edgeCurveGeometry(edge, positions, edgeFanTable.get(edge.id) ?? ZERO_FAN);
785
815
  if (geo === null) continue;
786
816
  dots.push({
787
817
  id: edge.id,
@@ -790,25 +820,75 @@ function RoundTableView(props) {
790
820
  });
791
821
  }
792
822
  return dots;
793
- }, [meeting, positions]);
823
+ }, [
824
+ meeting,
825
+ positions,
826
+ edgeFanTable
827
+ ]);
828
+ const connectNodes = (0, react.useCallback)((from, to, meetingId) => {
829
+ if (from === to) return;
830
+ rpc("roundtable/edge.add", {
831
+ meetingId,
832
+ from,
833
+ to,
834
+ direction: "forward"
835
+ }).then((result) => {
836
+ if (result.ok) {
837
+ setToast({
838
+ kind: "ok",
839
+ text: `已连接 ${from} → ${to}`
840
+ });
841
+ refresh();
842
+ } else setToast({
843
+ kind: "err",
844
+ text: result.error?.message ?? "连线失败"
845
+ });
846
+ }).catch(() => setToast({
847
+ kind: "err",
848
+ text: "连线失败:无法连接会议服务"
849
+ }));
850
+ }, [rpc, refresh]);
851
+ const dragging = drag !== null;
794
852
  (0, react.useEffect)(() => {
795
- if (drag === null) return;
853
+ if (!dragging) return;
854
+ let frame = 0;
855
+ let pending = null;
856
+ const flush = () => {
857
+ frame = 0;
858
+ if (pending === null) return;
859
+ const next = pending;
860
+ pending = null;
861
+ setDrag((previous) => previous === null ? null : {
862
+ ...previous,
863
+ x: next.x,
864
+ y: next.y
865
+ });
866
+ };
796
867
  const move = (event) => {
797
868
  const rect = containerRef.current?.getBoundingClientRect();
798
869
  const x = rect === void 0 ? event.clientX : event.clientX - rect.left;
799
870
  const y = rect === void 0 ? event.clientY : event.clientY - rect.top;
800
- setDrag((previous) => previous === null ? null : {
801
- ...previous,
871
+ const current = dragRef.current;
872
+ if (current !== null) dragRef.current = {
873
+ ...current,
802
874
  x,
803
875
  y
804
- });
876
+ };
877
+ pending = {
878
+ x,
879
+ y
880
+ };
881
+ if (frame === 0) frame = window.requestAnimationFrame(flush);
805
882
  };
806
- const up = (event) => {
807
- const from = drag.from;
808
- const meetingId = drag.meetingId;
809
- const x = drag.x;
810
- const y = drag.y;
883
+ const up = () => {
884
+ const current = dragRef.current;
885
+ dragRef.current = null;
811
886
  setDrag(null);
887
+ if (current === null) return;
888
+ const from = current.from;
889
+ const meetingId = current.meetingId;
890
+ const x = current.x;
891
+ const y = current.y;
812
892
  let bestKey = null;
813
893
  let bestDist = Number.POSITIVE_INFINITY;
814
894
  const hitRadius = 70;
@@ -821,37 +901,21 @@ function RoundTableView(props) {
821
901
  }
822
902
  const to = bestKey;
823
903
  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
- }));
904
+ connectNodes(from, to, meetingId);
844
905
  };
845
906
  window.addEventListener("mousemove", move);
846
907
  window.addEventListener("mouseup", up);
847
908
  return () => {
848
909
  window.removeEventListener("mousemove", move);
849
910
  window.removeEventListener("mouseup", up);
911
+ if (frame !== 0) {
912
+ window.cancelAnimationFrame(frame);
913
+ flush();
914
+ }
850
915
  };
851
916
  }, [
852
- drag,
853
- refresh,
854
- rpc,
917
+ dragging,
918
+ connectNodes,
855
919
  positions
856
920
  ]);
857
921
  const activeMessages = (0, react.useMemo)(() => {
@@ -1169,10 +1233,7 @@ function RoundTableView(props) {
1169
1233
  const to = positions.get(edge.to);
1170
1234
  if (from === void 0 || to === void 0) return null;
1171
1235
  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);
1236
+ const { fromDegree, fromIndex, toDegree, toIndex } = edgeFanTable.get(edge.id) ?? ZERO_FAN;
1176
1237
  const dx = to.x - from.x;
1177
1238
  const dy = to.y - from.y;
1178
1239
  const len = Math.max(1, Math.hypot(dx, dy));
@@ -1241,12 +1302,31 @@ function RoundTableView(props) {
1241
1302
  const rect = containerRef.current?.getBoundingClientRect();
1242
1303
  const x = rect === void 0 ? clientX : clientX - rect.left;
1243
1304
  const y = rect === void 0 ? clientY : clientY - rect.top;
1244
- setDrag({
1305
+ const next = {
1245
1306
  from: key,
1246
1307
  meetingId: meeting.id,
1247
1308
  x,
1248
1309
  y
1249
- });
1310
+ };
1311
+ dragRef.current = next;
1312
+ setDrag(next);
1313
+ };
1314
+ const activatePortByKeyboard = () => {
1315
+ if (kbConnectFrom === null) {
1316
+ setKbConnectFrom(key);
1317
+ setToast({
1318
+ kind: "ok",
1319
+ text: `已选中连线起点 ${key},请在目标节点的端口上按 Enter(Esc 取消)`
1320
+ });
1321
+ return;
1322
+ }
1323
+ if (kbConnectFrom === key) {
1324
+ setKbConnectFrom(null);
1325
+ return;
1326
+ }
1327
+ const from = kbConnectFrom;
1328
+ setKbConnectFrom(null);
1329
+ connectNodes(from, key, meeting.id);
1250
1330
  };
1251
1331
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1252
1332
  "data-rt-key": key,
@@ -1284,16 +1364,42 @@ function RoundTableView(props) {
1284
1364
  role: "button",
1285
1365
  tabIndex: 0,
1286
1366
  "aria-label": "connect",
1367
+ "aria-pressed": kbConnectFrom === key,
1287
1368
  "data-port-dir": dir,
1288
1369
  onMouseDown: (event) => {
1289
1370
  event.preventDefault();
1290
1371
  event.stopPropagation();
1291
1372
  startDrag(event.clientX, event.clientY);
1373
+ },
1374
+ onKeyDown: (event) => {
1375
+ if (event.key === "Escape") {
1376
+ if (kbConnectFrom !== null) {
1377
+ event.preventDefault();
1378
+ setKbConnectFrom(null);
1379
+ }
1380
+ return;
1381
+ }
1382
+ if (event.key !== "Enter" && event.key !== " ") return;
1383
+ event.preventDefault();
1384
+ event.stopPropagation();
1385
+ activatePortByKeyboard();
1292
1386
  }
1293
1387
  }, dir))
1294
1388
  ]
1295
1389
  }, key);
1296
1390
  };
1391
+ const overlayOpen = kbOpen || manageOpen || reviewOpen && meeting?.review !== null;
1392
+ const renderToast = () => toast === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1393
+ className: RoundTableView_module_css_default.toast,
1394
+ style: overlayOpen ? {
1395
+ position: "fixed",
1396
+ zIndex: 1300
1397
+ } : void 0,
1398
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1399
+ className: toast.kind === "ok" ? RoundTableView_module_css_default.toastOk : RoundTableView_module_css_default.toastErr,
1400
+ children: toast.text
1401
+ })
1402
+ });
1297
1403
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1298
1404
  className: RoundTableView_module_css_default.root,
1299
1405
  children: [
@@ -1507,13 +1613,7 @@ function RoundTableView(props) {
1507
1613
  })
1508
1614
  ]
1509
1615
  }),
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
1616
+ overlayOpen ? null : renderToast()
1517
1617
  ]
1518
1618
  }),
1519
1619
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
@@ -2442,6 +2542,7 @@ function RoundTableView(props) {
2442
2542
  })
2443
2543
  ]
2444
2544
  }) : null,
2545
+ overlayOpen ? renderToast() : null,
2445
2546
  fetchFailed === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2446
2547
  className: RoundTableView_module_css_default.fetchFailed,
2447
2548
  children: translate("fetchFailed")
@@ -2452,7 +2553,7 @@ function RoundTableView(props) {
2452
2553
 
2453
2554
  //#endregion
2454
2555
  //#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";
2556
+ 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: var(--dsw-alias-label-primary);\n}\n\n.dx5ni8t_select,\n.dx5ni8t_input {\n font-size: 13px;\n padding: 6px 10px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l1);\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n max-width: 260px;\n}\n\n.dx5ni8t_hint {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\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: 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: 12px;\n color: var(--dsw-alias-label-secondary);\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 font-size: 13px;\n padding: 7px 18px;\n border-radius: 8px;\n border: none;\n background: var(--dsw-alias-brand-primary);\n /* 保留 #ffffff:品牌实底上的按钮文字,13 个令牌里没有 on-brand 前景色。 */\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: var(--dsw-alias-state-success-primary);\n}\n\n.dx5ni8t_failed {\n font-size: 12px;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.dx5ni8t_note {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.dx5ni8t_sectionDivider {\n height: 1px;\n background: var(--dsw-alias-border-l2);\n margin: 2px 0;\n}\n\n.dx5ni8t_sectionTitle {\n font-size: 13px;\n font-weight: 700;\n color: var(--dsw-alias-label-primary);\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 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: 12px;\n color: var(--dsw-alias-label-secondary);\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 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 font-size: 12px;\n padding: 4px 12px;\n border-radius: 8px;\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: 12px;\n color: var(--dsw-alias-label-secondary);\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}\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: var(--dsw-alias-label-primary);\n}\n\n.dx5ni8t_presetMeta {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n line-height: 1.5;\n word-break: break-word;\n}\n\n.dx5ni8t_presetRoute {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\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 var(--dsw-alias-border-l2);\n background: transparent;\n color: var(--dsw-alias-label-secondary);\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: 8px;\n padding: 10px;\n border-radius: 8px;\n border: 1px dashed var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\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 var(--dsw-alias-border-l1);\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\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: var(--dsw-alias-brand-primary);\n /* 保留 #ffffff:品牌实底上的按钮文字,13 个令牌里没有 on-brand 前景色。 */\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 var(--dsw-alias-border-l2);\n background: transparent;\n color: var(--dsw-alias-label-secondary);\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: 6px;\n align-items: center;\n}\n\n.dx5ni8t_squadMemberRow .dx5ni8t_presetSelect {\n flex: 1 1 120px;\n min-width: 0;\n}\n";
2456
2557
  const classMap = {
2457
2558
  "root": "dx5ni8t_root",
2458
2559
  "field": "dx5ni8t_field",
@@ -3621,7 +3722,7 @@ function nodeLine(node) {
3621
3722
  //#endregion
3622
3723
  //#region src/client/tool-views.tsx
3623
3724
  const CARD = {
3624
- border: "1px solid rgba(128,128,128,0.35)",
3725
+ border: "1px solid var(--dsw-alias-border-l1)",
3625
3726
  borderRadius: 8,
3626
3727
  padding: "8px 10px",
3627
3728
  font: "12px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace",
@@ -3632,7 +3733,7 @@ const HEAD = {
3632
3733
  marginBottom: 4
3633
3734
  };
3634
3735
  const MUTED = { opacity: .75 };
3635
- const WARN = { color: "#e0a030" };
3736
+ const WARN = { color: "var(--dsw-alias-state-warn-primary)" };
3636
3737
  /** Card body for a parsed status payload. */
3637
3738
  function StatusCard({ data }) {
3638
3739
  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.3";
93
93
  /** 导出物头部使用的插件标识行。 */
94
94
  const PLUGIN_ID = `@y9931666/dsh-plugin-roundtable v${PLUGIN_VERSION}`;
95
95
  /** DeepSeek Harness 版本区间(导出头部展示用)。
@@ -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.3";
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.3";
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.3",
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
+ 装后刷新浏览器即可(本版只动浏览器半体);若宿主也升级了则按惯例重启。