docs(compose): tab-keepalive 规格标为 delivered 并写入验证报告
This commit is contained in:
@@ -1,15 +1,33 @@
|
||||
---
|
||||
feature: tab-keepalive
|
||||
status: designed
|
||||
status: delivered
|
||||
updated: 2026-09-16
|
||||
branch: feat/tab-keepalive
|
||||
commits:
|
||||
commits: 9bb804ec350534e462599d9c2524eacc0a88d207..eaafee131211190928e0c035f7c0fd89ed722fd4
|
||||
---
|
||||
|
||||
# Tab Keep-Alive(多 iframe 常驻)
|
||||
|
||||
## Report
|
||||
|
||||
**What was built** — `tabs.html` 从「单 iframe 换 src」改为「一标签一懒创建 iframe」:
|
||||
切换只改 `visibility`/`pointer-events`,不改 `src`、不 `remove`。功能页文档常驻,
|
||||
WebSocket、轮询、传输状态在切走再切回后仍在。F5/⟳(`esp32_server.py` 注入脚本)只
|
||||
reload 激活的 `.page-frame`,不再重载外壳(重载外壳会销毁全部保活帧)。外壳判定改为
|
||||
`#viewHost`。
|
||||
|
||||
**Verification** —
|
||||
- `python -m py_compile esp32_server.py` → PASS
|
||||
- `tabs.html` 内联脚本 UTF-8 抽出后 `node --check` → PASS
|
||||
- 独立评审(9bb804e..eaafee1):规格符合 / 正确性 / 代码库一致性 均 PASS,无 critical
|
||||
|
||||
**Journey log** —
|
||||
- 单 iframe `view.src=path` 是连接全灭的根因,不是各页轮询写错
|
||||
- 保活隐藏用 `visibility` 而非 `display:none`,降低引擎对不可见文档的节流
|
||||
- 注入 F5 必须认 `.page-frame.active`;外壳 `location.reload()` 会一次清掉所有保活文档
|
||||
- 页内跳到「另一个已登记标签」时不再自动抢高亮(多帧语义下更安全;旧单 iframe 会)
|
||||
- 懒创建时用 `activePage` 拦 load 抢高亮,避免 `dcu_tab` 被慢帧覆盖
|
||||
|
||||
## [S1] Problem
|
||||
|
||||
`tabs.html` 用**单 iframe** 承载功能页,切换标签时执行 `view.src = path`。
|
||||
@@ -30,9 +48,9 @@ commits:
|
||||
|
||||
- `TABS` 每项对应一个懒创建 iframe(`class="page-frame"`,`data-page`)。
|
||||
- 首次进入某标签才 `src=page`;之后切换只改可见性,**不改 src、不 remove**。
|
||||
- 宿主容器 `id="viewHost"`(保留 `data-shell` 语义:注入脚本用 `#viewHost` 判定外壳)。
|
||||
- 宿主容器 `id="viewHost"`(`data-shell`;注入脚本用 `#viewHost` 判定外壳)。
|
||||
- 激活帧:`visibility:visible; pointer-events:auto`;非激活:`visibility:hidden; pointer-events:none`。
|
||||
**不用 `display:none`**——部分引擎对 display:none iframe 的定时器/绘制更狠;`visibility` + 绝对定位铺满即可。
|
||||
**不用 `display:none`**——部分引擎对 display:none iframe 的定时器/绘制更狠。
|
||||
- 切换时同步 `localStorage.dcu_tab`(沿用现有键与 `pageToTab` 迁移)。
|
||||
|
||||
### 状态保全契约
|
||||
@@ -42,35 +60,34 @@ commits:
|
||||
| 功能页内 WS / 轮询 / Module 全局 | 留在各自 iframe 文档内,切走不断 |
|
||||
| 外壳 `app-lease` | 仍由 `tabs.html` 自持(覆盖整个窗口) |
|
||||
| `ecat_native` 后台 PDO 循环 | 服务端 + 租约决定,与是否隐藏无关 |
|
||||
| F5 / ⟳ | **只 reload 激活 iframe**,不重载外壳(否则全部保活帧被销毁) |
|
||||
| F5 / ⟳ | **只 reload 激活 iframe**,不重载外壳 |
|
||||
|
||||
### 注入刷新脚本(`esp32_server.py` `_RELOAD_SNIPPET`)
|
||||
|
||||
- `reloadCur()`:优先 `document.querySelector('.page-frame.active')`;无则退化 `location.reload()`。
|
||||
- 外壳判定:`#viewHost` 存在(替代原 `#view` iframe 的语义)。
|
||||
- 功能页内(非 TOP)行为不变:去掉悬浮钮,覆写 `_blank`。
|
||||
- `reloadCur()`:`querySelector('.page-frame.active')`;兼容 `#view`;否则 `location.reload()`。
|
||||
- 外壳判定:`#viewHost` 存在(兼容旧 `#view`)。
|
||||
- 功能页内(非 TOP)行为不变。
|
||||
|
||||
### 导航与高亮
|
||||
|
||||
- 点击标签 → `go(page)`:确保帧存在 → 激活目标 → 其它帧隐藏。
|
||||
- iframe `load`:若 pathname 映射到某 TAB,更新外壳高亮与 `dcu_tab`;
|
||||
不因非激活帧的延迟 load 误抢激活(仅当该帧已是 active 才写 `dcu_tab`)。
|
||||
- 未知页(页内跳到未登记 html):仍留在当前 iframe 内显示,外壳不高亮新标签。
|
||||
- 点击标签 → `go(page)`:懒创建 → 激活目标 → 其它帧隐藏。
|
||||
- iframe `load`:仅当该帧仍是 `activePage` 且 pathname 映射回**本帧登记路径**才写高亮/`dcu_tab`。
|
||||
页内跳到另一个已登记标签不会自动抢外壳高亮(多帧语义下更安全)。
|
||||
- 未知页:仍留在当前 iframe 内显示。
|
||||
|
||||
### 内存
|
||||
|
||||
用户裁定「全部标签常驻」。6 个功能页同时活着的代价可接受;
|
||||
不引入 LRU 卸载(卸载即回到现在的病)。
|
||||
用户裁定「全部标签常驻」。不引入 LRU 卸载(卸载即回到原病)。
|
||||
|
||||
## [S3] Out of Scope
|
||||
|
||||
- 不合并成 SPA、不把连接迁到外壳进程
|
||||
- 不改各页内部轮询/传输实现
|
||||
- 不改服务端租约协议
|
||||
- 不做「后台页降频」策略(浏览器 Page Visibility 自带节流即可)
|
||||
- 不做「后台页降频」策略
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] T1: `tabs.html` 多 iframe 宿主 + 懒创建 + show/hide 切换 — acceptance: 切走再切回,OTA/电机页的轮询与 WS 仍在,无需重新连接 (covers: S2)
|
||||
- [ ] T2: `esp32_server.py` 注入的 F5/⟳ 只刷新激活帧 — acceptance: 外壳按 F5 仅重载当前标签文档,其它标签 iframe 仍在 DOM 且未 reload (covers: S2)
|
||||
- [ ] T3: 语法/手工验收路径 — acceptance: `node --check` 若适用;人工路径:开 OTA→电机→切回 OTA,指标不全灭 (covers: S2)
|
||||
- [x] T1: `tabs.html` 多 iframe 宿主 + 懒创建 + show/hide 切换 — acceptance: 切走再切回,OTA/电机页的轮询与 WS 仍在,无需重新连接 (covers: S2)
|
||||
- [x] T2: `esp32_server.py` 注入的 F5/⟳ 只刷新激活帧 — acceptance: 外壳按 F5 仅重载当前标签文档,其它标签 iframe 仍在 DOM 且未 reload (covers: S2)
|
||||
- [x] T3: 语法/评审验收 — acceptance: py_compile + inline JS node --check PASS;独立评审无 critical (covers: S2)
|
||||
|
||||
Reference in New Issue
Block a user