Files
DCU_DesktopController/docs/compose/spec/tab-keepalive.md
T

4.4 KiB
Raw Blame History

feature, status, updated, branch, commits
feature status updated branch commits
tab-keepalive delivered 2026-09-16 feat/tab-keepalive 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。 这会销毁上一页的整个文档:

  • WebSocket(app-lease、调试通道)断开
  • setInterval / setTimeout 轮询停止
  • 电机页 EtherCAT/WiFi 传输状态、transport-ecat 的 started 标志丢失
  • OTA 页 ecat_load 轮询、SPI 状态轮询冷启动
  • 用户「调完一切、切走再切回」后连接全灭,表现为界面假死/全 --

外壳自身的 app-lease.js 仍在,但功能页内的连接与状态无法幸存。 这是结构性缺陷,不是某个页面的 bug。

[S2] Design

外壳:一标签一 iframe,只 show/hide

  • TABS 每项对应一个懒创建 iframe(class="page-frame",data-page)。
  • 首次进入某标签才 src=page;之后切换只改可见性,不改 src、不 remove。
  • 宿主容器 id="viewHost"(data-shell;注入脚本用 #viewHost 判定外壳)。
  • 激活帧:visibility:visible; pointer-events:auto;非激活:visibility:hidden; pointer-events:none。 不用 display:none——部分引擎对 display:none iframe 的定时器/绘制更狠。
  • 切换时同步 localStorage.dcu_tab(沿用现有键与 pageToTab 迁移)。

状态保全契约

状态 保活后
功能页内 WS / 轮询 / Module 全局 留在各自 iframe 文档内,切走不断
外壳 app-lease 仍由 tabs.html 自持(覆盖整个窗口)
ecat_native 后台 PDO 循环 服务端 + 租约决定,与是否隐藏无关
F5 / ⟳ 只 reload 激活 iframe,不重载外壳

注入刷新脚本(esp32_server.py _RELOAD_SNIPPET)

  • reloadCur():querySelector('.page-frame.active');兼容 #view;否则 location.reload()。
  • 外壳判定:#viewHost 存在(兼容旧 #view)。
  • 功能页内(非 TOP)行为不变。

导航与高亮

  • 点击标签 → go(page):懒创建 → 激活目标 → 其它帧隐藏。
  • iframe load:仅当该帧仍是 activePage 且 pathname 映射回本帧登记路径才写高亮/dcu_tab。 页内跳到另一个已登记标签不会自动抢外壳高亮(多帧语义下更安全)。
  • 未知页:仍留在当前 iframe 内显示。

内存

用户裁定「全部标签常驻」。不引入 LRU 卸载(卸载即回到原病)。

[S3] Out of Scope

  • 不合并成 SPA、不把连接迁到外壳进程
  • 不改各页内部轮询/传输实现
  • 不改服务端租约协议
  • 不做「后台页降频」策略

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: py_compile + inline JS node --check PASS;独立评审无 critical (covers: S2)