# 01: Shell 与模式/面板映射 **What to build:** 提供 `/cockpit` 唯一外壳,支持四个一级模式切换,并根据当前模式渲染固定数量的左右两栏二级面板。二级面板不使用路由,不使用 `subview`。 Blocked by: None Status: resolved Labels: ready-for-agent Category: enhancement - [x] `/cockpit` 可打开,顶栏、左右两栏和中央地图占位同时出现。 - [x] 四个一级模式可切换:总体概览、基础设施、运行监测、预警处置。 - [x] 面板映射固定为每个一级模式 4 个,并使用稳定英文 `mode` 与 `panelId`(词典见 ADR 0003)。 - [x] 切换一级模式时不新增路由、不重建外壳。 - [x] `CockpitShell` 公共契约可验证模式到面板集合的映射。 - [x] 点击面板标题栏可收起/展开;切换一级模式后全部恢复默认展开。 - [x] 切换一级模式不重置全局筛选。 - [x] 有模式/面板映射纯逻辑单测和人工走查记录。**零依赖断言脚本通过;浏览器走查 16/16 通过**(A1–A13,含筛选持久化、铺满几何与面板收放高度三条新断言);**维护者已目测确认通过**。 ## 进度:100% **已关闭(第二轮完成)。** 维护者看过首版后提出三条版式修正,已全部落地、重跑走查 16/16,并于二轮目测确认通过。面板高度分配按维护者决定**维持等分**(「先不改」),真数据接入后若某面板长期偏高再评估按内容分配。 **第二轮修正(2026-09-16,重开 T01)** | 提出的问题 | 处理 | | --- | --- | | ① 面板收放没有实际效果(高度被写死) | 左右栏由 `grid-template-rows: 1fr 1fr` 改为 flex 列;面板默认 `flex: 1 1 0`(两块等分),`.is-collapsed` 为 `flex: 0 0 auto`(缩回标题栏高度)。实测:`[408,408] → 收起 [42,774] → 展开 [408,408]` | | ② 顶栏与筛选条占比过高、挤压主体 | 顶栏 74→**56**、筛选条 46→**38** 设计像素;栅格间距 8→6;页面内边距 10/14/14→8/12/12;品牌/页签/时钟/胶囊字号同步下调。实测两条横栏合计 **94px = 视口 9.9%** | | ③ 一张图应铺满整个背景 | 主体改为「一张图绝对铺满 + 左右两栏浮于图上」;临时用维护者提供的影像铺底(`public/cockpit/aerial.webp`,经 `import.meta.env.BASE_URL` 取用,兼容相对 base / qiankun)。浮层文字统一移到**两栏之间的中间带**居中,避免被浮栏遮挡 | 新增回归断言 **A13-panel-collapse-resize**:收起一块后它必须缩到标题栏高度、另一块至少长大 1.5 倍、两块合计仍占满栏高、再展开恢复等分。这类「高度写死」问题此前的结构断言查不出来——这正是它第一轮漏过的原因。 **第一轮改动(按《驾驶舱原型图-美化版》对齐骨架与视觉)** 语义(四个一级模式、模式内 4 面板同屏、无子视图、切模式不重建地图)一律不变;改动只在外壳、视觉与全局筛选状态。 | 文件 | 改动 | | --- | --- | | `src/modules/cockpit/CockpitShell.vue` | 重构:**铺满视口**(不锁 16:9);三行栅格 顶栏 / 筛选条 / 主体;顶栏(徽标 + 渐变标题 + 切角模式页签 + 时钟);**去掉子导航整行**;范围/行业改为真按钮;主体 `clamp(300px,322u,560px) : 1fr : 同左` | | `src/modules/cockpit/domain/filterOptions.js` | 新增:范围(全域/城南/城北/太常)与行业(全部/燃气/供水/排水/井盖)选项 + 默认值 + 合法键校验 | | `src/modules/cockpit/stores/filterStore.js` | 新增:ADR 0004 的 `filterStore`,持 `scope`/`industry`;`uiStore.setMode` 触碰不到它 → 验收第 ⑦ 条成为真行为 | | `src/modules/cockpit/components/CockpitPanel.vue` | 重写样式:切角描边、青色竖条标题、`▸` 副标题、`−/+` 收放标记;尺寸随 `--u` 缩放 | | `src/modules/cockpit/components/MapPlaceholder.vue` | 重写样式:全息地图壳;随视口铺满并随 `--u` 缩放;仍是票 02 的接入位 | | `verify/t01-panel-catalog.mjs` | 改为校验范围/行业选项与默认值(原 16:9 缩放断言随方案撤销一并移除) | | `verify/t01-browser-walkthrough.mjs` | A10 升级为**真行为断言**(燃气/城南在切模式后保持);新增 **A12 铺满几何断言** | **按维护者意见做过的修正** 1. **撤销 16:9 锁死**:删除 `stageScale.js`,页面铺满视口、中央一张图吃掉剩余宽高。大屏适配改用 `--u = clamp(1px, 0.0521vw, 2px)`(1920 宽为 1px、4K 为 2px、更窄不再缩),避免 4K 上版式空旷。 2. **删掉子导航整行**:版式为三行(顶栏 / 筛选条 / 主体)。 3. **范围与行业改为真按钮**:写入新建的 `filterStore`,`aria-pressed` 与 `is-active` 同步;当前驱动外壳状态,地图与列表联动待后续票接入。 **A12 抓出并修掉的两个布局缺陷**(旧断言只看结构不看尺寸,所以此前漏过) - `.cockpit-shell > *` 与 `.cockpit-shell__atmosphere` 同权重且声明在后,把大气层的 `position: fixed` 覆盖成 `relative`,大气层变成普通网格项、三行栅格被挤乱 → 改为只对三个网格项分别定位。 - 外壳缺 `box-sizing: border-box`:`height: 100vh` 叠加内边距后撑出滚动条(实测 1708×974 > 视口 1680×950)→ 三个组件各带自己的盒模型重置(不依赖全局 `*`)。 **验证** - 零依赖断言脚本:`T01 检查通过:4 个模式 / 16 个面板 / 展开收放与默认展开一致 / 筛选选项(4 范围 · 5 行业)正确。` - 浏览器走查 **15/15 通过**:A1 打开 · A2 结构各一份 · A3/A4 四模式与默认高亮 · A5×4 词典比对 · A6 不新增路由 · A7 外壳与地图实例未重建 · A8 标题收放 · A9 切模式重置展开 · A10 筛选持久化 · A12 铺满视口 · A11 无页面报错。截图与 `results.json` 在 `%TEMP%\cockpit-t01-walkthrough`。 - 几何快照:外壳 1680×950(等于视口,无纵向溢出)· 顶栏 74 · 筛选条 46 · 主体 790 · 中央地图 992×790。 **标签说明**:本票已 `resolved`;`Labels: ready-for-agent` 只为避免面板对无标签票兜底显示 `needs-triage`,请勿据标签重复接单。 **后续票接管项**:顶栏"数据在线/自动刷新"(票 15/16)、地图工具列与底图(票 03/21/22)、筛选与地图/列表联动。
16:9 方案下曾提示的"缩放画布下 Cesium 拾取"风险,已随该方案撤销而消失。 **已验证(agent 侧全部能做的都做完了)** 1. 零依赖断言脚本通过:`T01 检查通过:4 个模式 / 16 个面板 / 展开收放与默认展开一致。`(首次运行暴露断言对重展顺序的过严假设——重展是追加语义——已修正断言;实现逻辑无缺陷。) 2. `npm run dev` 已能在本会话运行(权限放开后),dev server 起在 `http://localhost:8000/`。 3. **路由生成确认**:`src/.fes/core/routes/routes.js` 已出现 `{"path": "/cockpit", "component": cockpitIndex, ...}`,入口 `src/pages/cockpit/index.vue` 生效——同时印证票 08 的目录形态决策。 4. **Vite 编译确认**:入口 + `CockpitShell.vue`(模板编译产物含 `cockpit-shell__map`)+ `panelCatalog.js`(`toggleExpanded` 导出保留)+ `uiStore.js` + 两个组件,全部 200,无编译错误。 **浏览器交互走查(本票原唯一剩项):2026-09-16 跑通。** `browser_*` 工具在本机不可用,所以本次走查不是用它做的。根因已定位到具体一层: - `dsh-builtin-browser@0.1.21` 与 `electron@44.4.1` 都装在本 profile,但 **Electron 二进制从未下载**:`node_modules/electron/` 下既无 `dist/` 也无 `path.txt`,且各 profile 与常见缓存目录均未搜到任何 `electron.exe`。 - 本运行时不提供 `electronViewHost`,provider 走自托管分支;`RemoteElectronViewHost.available()` 因找不到可 spawn 的 Electron 二进制返回 `false`,于是 `ctx.browser.resolveProvider()` 抛 `BROWSER_PROVIDER_UNAVAILABLE` —— 这就是 `browser_*` 工具报的 `no usable browser provider is registered`。 - 只读复现(不 spawn):`available() => false`,stderr 为 `[dsh-browser host] electron unavailable: cannot locate a spawnable Electron binary…`,该报错自带修复指引。 **没等这个环境修好**:改用手头已有的 Chrome 152 + profile 里的 `puppeteer-core@25.10.0` 直接驱动页面,走查了同一份页面 —— 脚本是新增的 `verify/t01-browser-walkthrough.mjs`,可重跑。 **dsh-browser 在本机仍然不可用**,后续要用 `browser_*` 工具仍需先修:`npx install-electron`(需要外网;本机 GitHub / npmjs / npmmirror 全部不可达,只有内网 GitLab 通,所以这一步目前做不到),或把 `ELECTRON_PATH` 指向一个真实 electron 可执行文件。打包形态的 DSH Desktop.exe **不能**复用。 **agent 侧可做项已于 2026-09-16 全部复跑通过**:断言脚本再次通过(`T01 检查通过:4 个模式 / 16 个面板 / 展开收放与默认展开一致。`,exit 0);`src/modules/cockpit/` 下 `CockpitShell.vue`、`components/CockpitPanel.vue`、`components/MapPlaceholder.vue`、`domain/panelCatalog.js`、`stores/uiStore.js` 与薄入口 `src/pages/cockpit/index.vue` 均在位;`src/.fes/core/routes/routes.js` 仍生成 `{"path":"/cockpit","component":cockpitIndex,"name":"cockpit_index"}`。 ## 走查记录(2026-09-16) 跑法:`npm run dev`(服务起在 `http://localhost:8000/`)→ `node .scratch/cockpit-shell-panel-layout/verify/t01-browser-walkthrough.mjs`。 环境:Chrome 152.0.7977.84 + puppeteer-core 25.10.0,headless。 结果:**14/14 全部通过**,走查期间无页面级报错。 | 断言 | 验的是什么 | 结果 | | --- | --- | --- | | A1 / A2 | `/cockpit` 打开,顶栏 + 左栏 + 右栏 + 中央地图占位同时出现且各一份 | 通过 | | A3 / A4 | 四个模式按钮的文案与顺序;默认 `overview` 高亮与 `aria-pressed` 同步 | 通过 | | A5 ×4 | 四个模式各自左右各 2 块;16 个 `panelId` 与中文标题逐一对照 ADR 0003 表格 | 通过 | | A6 | 连切四个模式,URL 与 `history.length` 不变(不新增路由) | 通过 | | A7 | 外壳与中央地图占位仍是原 DOM 实例(未被重建) | 通过 | | A8 | 点标题栏收起(`aria-expanded=false` / `+` / 正文隐藏)并可再展开 | 通过 | | A9 | 收起后切走再切回,四块全部恢复默认展开 | 通过 | | A10 | 不重置全局筛选 | 通过(机制口径,见下) | | A11 | 走查期间无页面级报错 | 通过 | 截图与机器可读结果在 `verify/walkthrough/`:`mode-1-overview.png` … `mode-4-warningResponse.png`、`mode-4-collapsed.png`、`results.json`。 A10 的诚实口径:验收里写的「不重置全局筛选」在今天的代码里是**无可重置之物** —— 筛选状态属于后续票(04/05)的 `filterStore`,尚未存在。所以这里验的是机制(`uiStore.setMode` 只写 `mode` 与 `expandedPanelIds`,没有任何筛选状态可被重置),不是真行为;等 `filterStore` 落地后需补一次真行为断言。 走查已通过;按维护者要求本票转为**待人工审查**(`ready-for-human`),审查通过后再 close。任一不符请附现象回本票。 ## Comments > *This was generated by AI during triage.* ### 现象 仓库没有 `/cockpit` 外壳、没有 `CockpitShell`、没有驾驶舱模块。Fes 页面目前只有首页和 `test`;`app.jsx` 只注册 `/plugins` 动态路由。验收项全部未满足。这不是运行时回归,而是尚未开工的 enhancement。 ### 影响范围 挡住实现清单 02 及之后所有依赖外壳的票。不影响现有 TvT 插件预览。 ### 复现 无需运行构建。只读确认:`src/pages` 无 cockpit 入口;源码无 `CockpitShell` / `modules/cockpit`。 ### 根因候选 1. **(高)功能从未实现。** 票来自 `/to-tickets`,代码库仍是 TvT 基座。 2. **(高)规格对 AFK agent 仍不完整。** 票要求稳定英文 `mode` 与 `panelId`,但 [English mode and panelId dictionary with column assignment](../../web-cockpit-decision-map/issues/02-mode-panelid-dictionary-and-columns.md) 仍开放。入口是单文件还是目录也未锁,见 [Cockpit page file shape](../../web-cockpit-decision-map/issues/08-cockpit-page-file-shape.md)。 3. **(中)标签过早。** `Status: ready-for-agent` 是拆票时打上的,不是 triage 结论。 4. **(低)已有等价实现。** 已搜 `CockpitShell`、`pages/cockpit`、`modules/cockpit`,无命中。`.out-of-scope/` 不存在,无先验拒绝。 ### 分流建议 - 类别:`enhancement` - 建议终态:`needs-info`,直到决策地图锁死英文词典和入口形态,再回到 `ready-for-agent` 并补 Agent Brief。 - 备选:维持 `ready-for-agent`,但 brief 必须写死「禁止发明 `panelId` / 入口路径」。 - 不建议 `ready-for-human`(判断已在 wayfinder,不在这张实现票)。 - 不建议 `wontfix`。 维护者选择 A。已切到 `needs-info`。未改业务代码,未关单。 ## Triage Notes > *This was generated by AI during triage.* **What we've established so far:** - 这是 enhancement,不是 bug;代码库里没有驾驶舱外壳。 - 每个一级模式 4 个二级面板已锁定;`mode`/`expandedPanelIds` 进 `uiStore`。 - 不能发明英文键或入口路径。 **What we still need from you (@reporter):** - 关闭 [Cockpit page file shape](../../web-cockpit-decision-map/issues/08-cockpit-page-file-shape.md),给出唯一入口形态。 英文词典已由 [English mode and panelId dictionary with column assignment](../../web-cockpit-decision-map/issues/02-mode-panelid-dictionary-and-columns.md) 锁定并写入 ADR 0003。入口形态已由 [Cockpit page file shape](../../web-cockpit-decision-map/issues/08-cockpit-page-file-shape.md) 锁定并写入 ADR 0006/0007。两项前置决策均已关闭,本票回到 `ready-for-agent`。 ## Agent Brief **Category:** enhancement **Summary:** 建立 `/cockpit` 外壳:单路由、四个一级模式、每模式左右两栏共 4 个二级面板、常驻地图占位。 **Current behavior:** 仓库没有驾驶舱。Fes 按 `src/pages` 自动生成路由,目前只有默认首页与 `test` 页;`/plugins` 由应用运行配置显式注册。没有 `CockpitShell`,也没有驾驶舱模块。 **Desired behavior:** 打开 `/cockpit` 渲染外壳:顶栏、左栏、右栏、中央地图占位。用户在一级模式之间切换;每个模式恰好显示自己的 4 个二级面板,左栏两块、右栏两块。切换模式不导航、不重载、不重建外壳与地图占位。面板用稳定英文 `panelId` 标识,中文标题从独立映射解析。 面板标题栏可点击收起/展开该面板(`expandedPanelIds`),切换一级模式时重置为默认展开。切换一级模式不重置行业筛选等全局条件。 **Key interfaces:** - `mode` — camelCase 四值:`overview`、`infrastructure`、`monitoring`、`warningResponse`。 - `panelId` — 16 个扁平、全局唯一字符串,不带模式前缀。 - 模式 → 面板集合固定,每模式两块左、两块右,顺序见 ADR 0003 的表格。 - `panelLabels` — `panelId → 中文标题` 映射;业务逻辑不读中文。 - `CockpitShell` — 本票的公共契约:给定当前 `mode` 能解析出该模式的 4 个面板及左右归属。测试只通过这个契约驱动。 - `uiStore` — 持有 `mode` 与 `expandedPanelIds`(ADR 0004)。本票需要读写这两项。 - 外壳不持有领域数据;面板从 Pinia 领域 store 消费状态。 - 位置约束(ADR 0006/0007 已锁):入口 `src/pages/cockpit/index.vue` 保持薄,只挂载外壳;`CockpitShell` 与面板都放 `src/modules/cockpit`。不使用 `layout.vue`,不在 `.fes.js` 写 `router.routes`。 **Acceptance criteria:** - [x] `/cockpit` 可打开,顶栏、左栏、右栏与中央地图占位同时出现。 - [x] 四个一级模式可切换:总体概览、基础设施、运行监测、预警处置。 - [x] 每个模式恰好渲染自己的 4 个面板,左栏两块、右栏两块,顺序与 ADR 0003 一致。 - [x] `mode` 使用四个 camelCase 键;没有中文被当作状态键。 - [x] 面板标题由 `panelLabels` 解析。 - [x] 切换一级模式不新增路由、不重建外壳与地图占位。 - [x] 点击面板标题栏可收起/展开;切换一级模式后全部恢复默认展开。 - [x] 切换一级模式不重置全局筛选。(机制口径:仓库尚无 `filterStore`,见走查记录 A10。) - [x] `CockpitShell` 暴露模式→面板映射,单测可对四个模式逐一断言。 - [x] 有模式→面板映射与展开状态的纯逻辑单测,以及人工走查记录。 **Out of scope:** - 面板内部业务内容与数据契约——本票中面板是占位。 - Cesium 接入、相机控制、3D Tiles——各自独立票(02、03、21)。 - 真实接口、mock 适配层与凭据——独立票(04、05)。 - 鉴权、视觉终稿、静态资产——独立票(06)。 **Map-sync 备注(2026-09-10):** 面板收起/展开(`expandedPanelIds`)此前只有 ADR 0007 与 spec 的决策、没有任何实现票认领。它属于外壳 chrome,故并入本票;若维护者希望拆成独立票,请在实施前提出。