# 任务输出常驻浮动窗 + 节点卡去竖线 + 手机设置 日期:2026-09-27 分支:`feat/tasks-graph-polish`(承接 PR #680 的打磨线) ## 背景(用户原话) > 1. 后台任务弹窗没有高度限制,输出很长会导致看不到。同时让其变为常驻的全屏弹窗(带关闭/拉伸,建议抽取为全局组件方便后续复用) > 2. 优化节点UI,加上圆角,同时取消左侧装饰条,让整个节点看起来更为统一 > 3. 设置中新增手机设置,里面放各种和手机端的适配设置,目前加上这么几项:检测到处于手机状态时,有新的后台任务或Agent任务时不会自动弹出,任务管理的默认状态切换为树状图 ## 用户确认的决策(问答摘要) | # | 问题 | 结论 | |---|---|---| | 1 | 弹窗形态 | 可拖拽 + 可拉伸的**常驻浮动窗**(默认约视口 70%×60%,最小 320×240) | | 2 | 复用范围 | 抽成**全局组件**,本轮只把后台任务输出换过去(节点/工作流详情、任务窗保持现状) | | 3 | 常驻边界 | 只靠**关闭按钮 / Escape**;外部点击、失焦、锚点离屏都不关;切会话/关标签、任务结算后都保留 | | 4 | 左侧竖条 | 「就是左侧的竖条,这个去掉」 | | 5 | 层级组色 | 「这个可以留着,只是那个竖线不要了」 | | 6 | 当前会话标记 | 「边框加粗高亮即可」 | | 7 | 圆角 | **8px**,并**保留类型描边**(成员 accent 描边、工作流 accent 描边 + 底色) | | 8 | 手机设置 | **自动检测(沿用 768px 窄屏判定)+ 两个独立开关(默认都开)**,分组放在设置页 | ## 架构与文件 ``` 新增 src/client/FloatingWindow.tsx 常驻浮动窗(拖拽 + 拉伸 + 关闭,内容区自滚动) src/client/FloatingWindow.module.css 窗框/标题栏/内容区/底部行/三个拉伸柄样式 tests/floating-window.spec.tsx 9 例:结构、尺寸与定位、关闭契约、两个手势 改写 src/client/JobsDrawer.tsx JobOutputPopoverContent → JobOutputWindow src/client/SubagentView.tsx 任务分支改渲染窗口;默认视图加窄屏覆盖 src/client/tasks-graph.module.css 卡片:去两条左侧竖条、圆角 8px、当前会话 1.5px 描边 src/client/sidebar/use-host-feeds.ts 两个自动弹出 gate 加手机压制 src/client/SideCardSection.tsx 新增「手机」分组(两行开关) src/prefs-shared.ts / src/config.ts / src/client/prefs.ts 两个新偏好 mobileNoAutoOpen / mobileDefaultTree src/client/locales*.ts(20 份) 5 条手机设置词条 ``` ## 关键设计 ### 1. 常驻浮动窗(`FloatingWindow.tsx`) - **为什么不是复用 `AnchoredPopover`**:那是 popover —— 锚定元素、按内容定尺寸、外部 mousedown / window blur / 锚点离开视口都会关它。被**持续观察**的面(一个跑几分钟的任务输出)需要的恰好相反的生命周期:只有关闭按钮与 Escape 结束它,而且必须能挪开、能拉伸。 - **内容区就是高度上限**:`data-window-body` 自己 `overflow: auto`,所以「输出很长看不到」这条被结构性修掉(旧 popover 完全没有高度约束,`.popPre` 那个 160px 滚动井在改用 `TerminalBlock` 之后已成死规则 —— 本轮一并删掉)。 - **手势复用既有范式**:移动走 window 级 `pointermove`(同 `AnchoredPopover` 的拖拽,容器捕获会重定向 click、杀掉窗内按钮),拉伸走 `setPointerCapture` + `createFrameBatcher()`(同 `EditorHost` 的面板拉伸;逐事件 setState 是 #315 的拖拽卡顿)。 - **几何**:`position: fixed`;尺寸在**挂载时量一次**(之后每条流式输出帧都不会挪动读者摆好的窗);拖拽保证标题栏至少 48px 留在视口内;拉伸夹取到 `minSize` 与视口;window `resize` 时把窗夹回视口。 - **常驻**:不注册外部 mousedown / blur / IntersectionObserver;只注册 Escape(捕获阶段)。 - **皮肤契约**:令牌全量、8px 圆角与节点卡一致、浮动层保留 `--dsw-shadow-lv3`。 ### 2. 任务输出接入 - `JobOutputWindow`:标题栏(任务 label + 复制按钮 + 关闭)/内容区(状态行 + `TerminalBlock`,`maxLines: Infinity` 保留「看完整输出」)/底部行(跟随最新 + 两击终止)。 - **顺带修掉的 bug**:「跟随最新」原先滚的是包在 `TerminalBlock` 外面的普通 `div`(永不滚动)——现在滚窗口内容区。 - 页面侧:`popover.kind === 'job'` 不再走 `AnchoredPopover`;节点/工作流详情仍是 280px 锚定小卡,任务窗不变。 - 任务从宿主 roster 消失时窗口关闭(无处再观察);**结算不掉行,窗口继续**。 ### 3. 节点卡(`tasks-graph.module.css`) | 变更 | 说明 | |---|---| | 删除 `.cardTop::before` | 层级竖条;层级改由上段极淡底色承担,并把色调从 7/5/3.5/2.5% 抬到 **9/7/5/3.5%** 补偿对比度损失 | | 删除 `.nodeCurrent::before` | 当前会话竖条;`.nodeCurrent` 改为 **2px accent 描边**(「边框加粗高亮」) | | `.node` 圆角 4px → **8px** | 与新的浮动窗同一圆角,卡与窗成为一个家族 | | `.cardTop` 左内边距 10px → 8px | 竖条消失后的回正 | | `.nodeTeam` / `.nodeWorkflow` / `.nodeFold` / `.nodeError` | 按用户选择**保留**类型区分;只继承新圆角 | | 删除 `.popPre` / `.jobPopPre` | 随旧弹窗一起死掉的规则 | **几何安全性**:`.node` 是 `box-sizing: border-box` + 布局给的 `min-height`,加粗描边被吸收在盒内,卡片外框尺寸不变 —— `tasks-graph-layout.ts` 的高度/行距预算**不需要改**,`tests/tasks-graph-layout.spec.ts` 保持全绿。 ### 4. 手机设置 | 键 | 默认 | 语义 | |---|---|---| | `mobileNoAutoOpen` | true | 窄屏时**同时**压制子代理与后台任务两个自动弹出(既有两个开关的值不被改写;宽屏行为不变) | | `mobileDefaultTree` | true | 窄屏时任务页默认树状图(页内切换仍是临时覆盖,优先级最高) | - 判定沿用插件既有的 `isNarrowWidth`(768px 断点,与侧边栏手机布局、park 逻辑同源),在**触发时**读视口。 - 设置 UI:DSH 设置页「侧边卡片」分区新增「手机」分组(两行 title/desc + 宿主 `Switch`,走既有 `applyPref` 写入)。 ## 实施偏差记录(写给复审) 1. **窗口尺寸只在挂载时量一次**(`useEffect(..., [])` + 一条 eslint-disable):若跟着 props/内容重算,每条流式输出帧都会把读者拖好的窗口弹回去。视口变化仍会夹取。 2. **`aria-label` 用 `