222 lines
20 KiB
Markdown
222 lines
20 KiB
Markdown
# 77 · 回到页面自检 + 就地恢复:把「恢复过程」做成看得见的
|
||||
|
|
|
|||
|
|
- 日期:2026-09-13
|
|||
|
|
- 状态:✅ **已落地并验证**(服务器 `src/supervisor/proxy.ts` + `lib/` 已构建,`dshs` 已重启)
|
|||
|
|
- 触发:用户「**不自然的地方是我回到页面根本没看到过拉起实例的提示**」(承接档案 72 的「回到页面没反应」)
|
|||
|
|
- 结论一句话:**能检测,但判据选错了** —— 探活只问「进程在不在跑」,而平台把 `starting` 也算在跑、崩溃后新实例 **1 秒内**就起来 ⇒ 用户切回页面时几乎永远命中「没问题」,**既不提示也不恢复**。本次把判据换成「页面还能不能连上实例」,并把恢复过程做成**可见**的,同时**不再跳到门户域**。
|
|||
|
|
- 关联:档案 72(回到页面自动唤醒)/49(导航过渡页)/50(注入脚本)/51(401 透明重放)/59(慢请求提示)/24(导航 401 恢复)
|
|||
|
|
|
|||
|
|
> **TL;DR**|**结论**:提示"看不到"不是没做,是**判据失效 + 提示只活 600ms**。
|
|||
|
|
> **关键**:① 判据改为「实例侧探针」(`getting starting` 也必然超时 → 能被识别)② 顶部轻提示条 + 不自动消失的覆盖层 ③ 恢复**就地**,不再跳门户过渡页。
|
|||
|
|
> **状态**:✅ 已部署(配置层面全绿;**浏览器观感待用户实测**)。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 一、根因(三条,都从代码与生产日志取证)
|
|||
|
|
|
|||
|
|
| # | 根因 | 证据 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| **主** | 注入脚本 `probe()` 只在 `j.running === false` 时恢复;而 `running` 来自 `src/web/routes/dsh.ts` 的 `alive(status)` = `status !== 'crashed' && !== 'stopped' && !== 'failed'` ⇒ **`starting`(正在启动)也算"活着"** | 源码;且实测每次崩溃后新 scope 在**同一秒或 1 秒内**起来(23:37:46→23:37:46、23:41:06→23:41:07、23:45:30、23:46:46、23:48:46、23:50:51)⇒ 用户切回标签页时实例几乎总是 `starting`/`running` ⇒ **判据永远不动手** |
|
|||
|
|
| 次 | **即便触发,提示只活 600 ms** 就 `location.replace(wake.html)`;实例已在跑时过渡页的 `enter` **立即返回** ⇒ 过渡页一闪而过,肉眼看不到 | 旧 `recover()`;`dsh.ts` `/api/dsh/enter` 的"复用已运行实例"分支 |
|
|||
|
|
| 次 | 旧路径 **跳门户域**,且 `recover()` 跳的 `wake.html` **不带 `next`** ⇒ 恢复后落回**根地址**、地址栏换域名、页面状态全丢 | 档案 72 §三 |
|
|||
|
|
|
|||
|
|
## 二、方案取舍
|
|||
|
|
|
|||
|
|
| 方案 | 做法 | 判定 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| A 现状(只判进程) | 只在 `running:false` 恢复 | ❌ 判据与"页面能不能用"不等价 ⇒ 主场景不触发(本次要修的) |
|
|||
|
|
| B 周期性轮询(如每 30 秒探活一次) | 定时器主动探 | ❌ 每个在线用户每分钟多次请求,且**用户在场却不操作时毫无收益**;档案 72 §五已否 |
|
|||
|
|
| **C 探针 + 可见反馈 + 就地恢复(采用)** | 回到页面才探;门户口判进程 + **实例侧探针**判连通;提示条→覆盖层;恢复就地 | ✅ 只在"用户真的回到页面"这一个时机付出成本;失败即**可见**;不换域名、保留路径 |
|
|||
|
|
| D 保活(页面可见即上报活跃,防被 LRU 挤掉) | 定时上报 | ⏸ **本次不做**:会改变常驻实例的抢占语义(`cap=4` 下会挤掉别人),需单独评估容量,另开档案 |
|
|||
|
|
|
|||
|
|
## 三、实现(唯一改动文件 `src/supervisor/proxy.ts`,改的都是注入脚本 `SESSION_RECOVERY_JS`)
|
|||
|
|
|
|||
|
|
| 改动 | 内容 |
|
|||
|
|
|---|---|
|
|||
|
|
| **新增探针 `poke()`** | 同源 `GET /`,`cache:'no-store'`、`redirect:'manual'`、**2.5 秒超时**(`AbortController`)。`200` → `ok`;`404/405` → **`unknown`(官方若改了入口形态就退回旧判据,绝不误报)**;其余 → `bad` |
|
|||
|
|
| **判据升级** | 门户口 `status` 说 `running:false` → 直接恢复;否则再跑 `poke()`,`bad` 才恢复 |
|
|||
|
|
| **提示可见** | 新增 `ensureBar/showBar/hideBar` → 顶部轻提示条 `#__dshConnBar`(不遮罩);**至少显示 900 ms** 再撤(否则一闪而过等于没提示) |
|
|||
|
|
| **触发细分** | 只有"**离开 ≥20 秒**又回来"(`visibilitychange` + `leftAt`)才亮提示条;瞬时切换静默探活。`focus` 静默、`pageshow(persisted)` 带提示 |
|
|||
|
|
| **就地恢复** | `recover()` 不再跳 `wake.html`,改为跨域 `POST /api/dsh/enter`(带 cookie)拿**带新 token** 的地址 → `withPath()` 保留 `pathname`/`hash` → `location.replace()`;取不到则回落 `location.reload()` |
|
|||
|
|
| **文案去归因** | 「实例已休眠(**空闲回收**)」→「工作区已休眠,正在唤醒…」;「会话已过期(实例被回收重建)」→「正在重新连接你的工作区…」 |
|
|||
|
|
|
|||
|
|
**未新增**:后端端点、DB 字段、依赖;**未改动**:导航路径的 302 → `wake.html`(刷新/首次进入仍走过渡页,**行为不变**)、401 既有路径、`wake.html` 本体。
|
|||
|
|
|
|||
|
|
> 复用而非新建(U2):探针用**实例自己的 `GET /`**(平台无新端点);恢复复用既有 `/api/dsh/enter`。
|
|||
|
|
|
|||
|
|
## 四、验证
|
|||
|
|
|
|||
|
|
| 项 | 方式 | 结果 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| 本地类型检查 | `npm run build`(tsc) | ✅ 退出码 0 |
|
|||
|
|
| **注入脚本不变量** | 模板字符串内**反引号 = 0 / `${` = 0** | ✅(长度 6604 → 10634 字符) |
|
|||
|
|
| **注入脚本语法** | 从编译产物抽 `SESSION_RECOVERY_JS`/`SESSION_ASSIST_JS` → `new Function()` | ✅ 双双通过 |
|
|||
|
|
| **CORS 前提**(本方案命门) | `OPTIONS https://alotbuy.com/api/dsh/enter`,`Origin: https://admin.alotbuy.com` | ✅ `204` + `allow-origin: https://admin.alotbuy.com` + `allow-credentials: true` + `allow-headers: Content-Type`;无 cookie 的 POST → `401` **且带 CORS 头** |
|
|||
|
|
| 双端一致性 | `md5sum` 本机 vs 服务器 + 服务器 `CR 行数 = 0` | ✅ `8c21a885…` 双向一致,LF 保持 |
|
|||
|
|
| 服务 | `systemctl restart` → `is-active` / 门户 / 残留 scope | ✅ `active` / `200` / **0 个** |
|
|||
|
|
| **注入落位** | 临时会话(`mksess.cjs`,用完即删)→ `enter` → 三件套 curl 取实例页 | ✅ `enter_http=200`、实例 `running`、页面 `200`;`__dshConnBar`/`function poke`/`withPath`/`rawFetch`/`leftAt`/「正在检查工作区连接」/「工作区正在恢复」**全部命中**;旧文案「会话已过期(实例被回收重建)」**= 0** |
|
|||
|
|
| 清理 | 临时会话删除 | ✅ `deleted_sessions=2`(含历史残留) |
|
|||
|
|
|
|||
|
|
**未做(L5)**:浏览器端观感 —— 「切回标签页 → 顶部出现『正在检查工作区连接…』」、「实例确实不可用时 → 覆盖层 → 原地回到原路径」。**需用户实测**(本机无 Chromium,装它成本不成比例)。
|
|||
|
|
|
|||
|
|
### 验证顺带纠正的一条误报
|
|||
|
|
`[inject-recovery] skip: content-encoding=gzip` 全历史 11 次、最近两次(09-12 22:40 / 23:26)**紧邻 `[proxy-auth-replay]`**,`remoteAddress` 一次是**服务器本机 IP**、一次是 CF 回源 ⇒ 是**我们自己的验证 curl**(默认 `Accept: */*`,不含 `text/html` ⇒ 代理不覆盖 `accept-encoding: identity` ⇒ 上游 gzip ⇒ 跳过注入)。**真实浏览器导航必带 `text/html`**(实测注入正常)⇒ **不是用户侧的问题,本次无需处理**。
|
|||
|
|
|
|||
|
|
## 五、边界(有意不做)
|
|||
|
|
|
|||
|
|
1. **恢复后不恢复页面内状态**(输入草稿 / 滚动位置)——同档案 72 §五,抓 DOM 状态会随官方前端升级而碎。
|
|||
|
|
2. **不做周期性探活**(只在"回到页面"付出成本)。
|
|||
|
|
3. **不做保活**(见 §二 方案 D,需单独评估容量)。
|
|||
|
|
4. 探针 **2.5 秒超时**是取舍:把"实例在启动中"判成 `bad` 正是我们要的;代价是**网络抖动 + 实例侧同时慢**时可能多一次原地重载(`status` 走门户、`poke` 走实例,两条链路同时慢才可能命中)。
|
|||
|
|
|
|||
|
|
## 六、红线遵守
|
|||
|
|
|
|||
|
|
- **R8**:重启 `dshs` → 当时 **2 个**在线实例被停(约数秒);**执行前已获用户明确授权**("现在改")。
|
|||
|
|
- **R2**:纯注入脚本,不改官方 dsh 主程序与缓存;不落盘。
|
|||
|
|
- **R7**:只改 **1 个源文件**;改动前用 `md5`/`diff` 确认服务器版与本地版**完全一致**(排除了把在途改动带上生产的风险)。
|
|||
|
|
- **R9**:抢锁失败时**停手并报告**;本次解锁是**用户明确点头**后执行(`--release-exec` + `FORCE=1 release univer-fork-deploy`),**未在本会话内自行判定对方死活**。
|
|||
|
|
- 未 commit / 未 push。
|
|||
|
|
|
|||
|
|
## 七、回滚
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cp -a /opt/dsh/backups/proxy.ts.bak-20260913-0012 /opt/dshs/src/supervisor/proxy.ts
|
|||
|
|
cd /opt/dshs && npm run build && systemctl restart dshs
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
(`wake.html` 未被改动,导航路径行为不变,回滚面仅注入脚本本身。)
|
|||
|
|
|
|||
|
|
## 八、遗留(另行报告)
|
|||
|
|
|
|||
|
|
1. ~~**崩溃熔断可无限重来**:熔断后 `mains.delete` + `resetCrashState()`,而用户下次 `launch()`/`enter` 又 `resetCrashState()` ⇒ 计数清零、重新给足预算;且熔断**只写 stderr、无告警**。~~ → ✅ **2026-09-13 已由档案 78 处置**(跨轮熔断态 + 指数冷却 + 双通道告警;代码完成、本地全绿,**部署待 R8 窗口**)。
|
|||
|
|
2. `INDEX.md §二` 的状态摘要("档案 72 份" + 各状态计数)与表格实际行数(76)**已不一致**(本次未改,属历史漂移)。
|
|||
|
|
3. 实例被反复重启的**外部诱因**已查明(并行会话迭代插件版本),与本档无关。
|
|||
|
|
|
|||
|
|
## 九、补充(2026-09-13 07:0x):**浏览器端实测已完成**(关闭 §四 的 L5)
|
|||
|
|
|
|||
|
|
§四 写的「未做(L5):浏览器端观感 —— 需用户实测」**已补做**:本机真 Chrome(无头,`playwright-core` 驱动)+ **临时会话**(`mksess.cjs` 直插,用完即删)打开 `https://admin.alotbuy.com/`,用**真实事件**(`visibilitychange` / `pageshow(persisted)`)+ **网络层拦截**触发两条路径。**未重启服务、未停实例、未改任何源文件**。
|
|||
|
|
|
|||
|
|
| # | 场景 | 手段 | 结果 |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| 0 | 注入落位 | 直接加载实例页 | ✅ `__dshRecover=1`;`__dshConnBar` / `function poke` / `function withPath` / `LEFT_MS` **全部命中**;旧文案「空闲回收」= **0** |
|
|||
|
|
| A | **离开 ≥20 s 又回来** | 覆写 `document.visibilityState` 后派发 `visibilitychange`(hidden),21 s 后派发 (visible) | ✅ 顶部条出现「**正在检查工作区连接…**」,**约 1 s 后自动撤除**;**全程未误亮覆盖层** |
|
|||
|
|
| B | **页面与实例已脱节** | 拦截实例侧 `fetch /` 使其失败 → 派发 `pageshow(persisted)` | ✅ 覆盖层出现「工作区正在恢复,请稍候…」→ `POST /api/dsh/enter` **200** → 覆盖层撤除 → **仍在 `admin.alotbuy.com`(未跳门户域)** |
|
|||
|
|
|
|||
|
|
**证据截图**:`_patch77/shots/`(`A-顶部提示条.png`、`B-恢复覆盖层.png`、`B-恢复后.png`、`0-初始.png`)。
|
|||
|
|
|
|||
|
|
> **第一次跑出现两个「❌」,复盘后确认都是测量脚本的错,不是产品的错**:① 提示条等待器在 21 s 等待**之前**就启动,被自身 6 s 超时耗光 → 必然抓不到;② 网络过滤写成 `hostname.endsWith('.alotbuy.com')`,**漏掉裸域 `alotbuy.com`**(`/api/dsh/enter` 在门户裸域上)→ 误报「没调 enter」。修正后 **10/10 全绿**。
|
|||
|
|
|
|||
|
|
**结论**:档案 72/77 的「回到页面自检 + 就地恢复」链路在**真实浏览器**里行为符合设计(含「看得见」这一条);**§四 的 L5 关闭**。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 追加(2026-09-13 08:1x · **浮层视觉升级**):「转圈」→「AI 核心」
|
|||
|
|
|
|||
|
|
**触发**:用户「工作区已释放正在恢复那个浮层的**转圈动画效果太简陋**,看看能否更科技化 AI 化」。
|
|||
|
|
|
|||
|
|
**旧实现**:一个 `border:3px solid rgba(255,255,255,.25); border-top-color:#fff` 的经典转圈(34×34 / 0.8s linear)—— 能看,但确实"工地"。
|
|||
|
|
|
|||
|
|
**新实现**(纯 CSS + `createElement`,零外部资源、零依赖):
|
|||
|
|
|
|||
|
|
| 元素 | 作用 |
|
|||
|
|
|---|---|
|
|||
|
|
| `.__dsh-ov` | 覆盖层:双层径向光晕 + `rgba(9,11,16,.9)` 深底 |
|
|||
|
|
| `.__dsh-card` | 半透明卡片 + 渐变描边 + `backdrop-filter: blur(9px)` |
|
|||
|
|
| `.__dsh-halo` | 呼吸光晕(2.6s 缩放/透明度) |
|
|||
|
|
| **`.__dsh-arc`** | **conic 渐变扫描弧**(蓝 → 紫),用 `mask` 挖空成环、1.5s 旋转 —— 视觉主角 |
|
|||
|
|
| `.__dsh-arc2` | 反向虚线环(7s,与主弧**反差速**,产生"仪器感") |
|
|||
|
|
| `.__dsh-core` | 脉冲核心(1.6s 缩放 + 透明度) |
|
|||
|
|
| `.__dsh-dots` | 三颗轨道粒子(各自 0.35s 延迟 + 三色) |
|
|||
|
|
| `.__dsh-msg` | 文案:极弱流光扫过(**可读性下限 0.94 不透明度**,不会像灰掉) |
|
|||
|
|
| `.__dsh-label` | `DSH · AUTO RECOVERY` 等宽小字 |
|
|||
|
|
| `.__dsh-track` | 不定进度条(scan) |
|
|||
|
|
|
|||
|
|
**无障碍**:`@media (prefers-reduced-motion: reduce)` 关闭全部动画(已出 reduced-motion 截图确认不是"卡住")。动画只用 `transform` / `opacity` / `background-position`,不触发布局抖动。
|
|||
|
|
|
|||
|
|
**硬约束遵守**(注入脚本的三条红线):
|
|||
|
|
1. 反引号 **0** / `${` **0**(CSS 里字体名改用单引号 `'Segoe UI'`,从而 JS 侧可用双引号字符串承载 CSS 各行);
|
|||
|
|
2. **不用 `innerHTML`**(实例页可能启用 Trusted Types)→ 结构全部 `createElement`;
|
|||
|
|
3. 样式集中在 `#__dshRecoverCss` 一次性注入;DOM 仍**只建一次**(否则每次改文案会重建元素、打断动画 —— 这是档案 77 原有教训,本次沿用)。
|
|||
|
|
|
|||
|
|
**验证**:
|
|||
|
|
|
|||
|
|
| 项 | 结果 |
|
|||
|
|
|---|---|
|
|||
|
|
| `npm run build`(tsc) | ✅ rc=0 |
|
|||
|
|
| `npm test` | ✅ 45 pass / 0 fail / 1 skipped |
|
|||
|
|
| 注入脚本不变量 | ✅ 编译产物抽 `SESSION_RECOVERY_JS`/`SESSION_ASSIST_JS` → `new Function()` **双通过**(反引号 0 / `${` 0) |
|
|||
|
|
| **端到端**(临时会话 + 三件套 curl 取实例页) | ✅ `__dsh-ov` / `__dsh-orb` / `__dsh-arc2` / `__dsh-core` / `__dsh-track` / `conic-gradient` / `prefers-reduced-motion` / `DSH · auto recovery` **全部命中**;旧标记 `border-top-color` = **0**;临时会话已删 |
|
|||
|
|
| 部署 | ✅ 备份 `proxy.ts.bak-overlay-20260913-0815` → 服务器 build rc=0 → 重启(PID 413817→**415625**,门户 200) |
|
|||
|
|
|
|||
|
|
**预览与证据**:`_patch77/overlay-preview.html`(**与注入样式同源** = `_patch77/overlay.css`,避免"预览和线上不一样")+ 截图 `_patch77/shots/overlay-{new,closeup,reduced-motion}.png`。
|
|||
|
|
|
|||
|
|
**本次未动**:顶部提示条 `#__dshConnBar` 的视觉(用户只点了浮层)—— 若要一起统一风格,另说。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 追加(2026-09-13 08:2x · **现场故障 + 热修**):注入脚本被我自己写崩了
|
|||
|
|
|
|||
|
|
**用户报告**:「admin 的 dsh 主页面断开连接了,但页面没显示『工作区已休眠正在唤醒』浮层,模型一直显示连接异常」。
|
|||
|
|
|
|||
|
|
**根因(我引入的)**:08:15 那次浮层视觉升级里,我写了 `st.textContent = [...].join('\n');` ——
|
|||
|
|
这段代码位于 **TS 模板字面量** `SESSION_RECOVERY_JS` 内部,`\n` 在**模板求值时**就被转成**真换行**,
|
|||
|
|
注入到浏览器的那段 JS 变成 `].join('` + 裸换行 + `');` ⇒ **SyntaxError** ⇒
|
|||
|
|
**整个注入脚本不执行** ⇒ 浮层 / 自检 / 自愈全部静默失效,页面只剩 dsh 自己的「连接异常」。
|
|||
|
|
|
|||
|
|
**为什么我的"校验"没拦住**:当时只做「从 `lib/` 抽**原始文本** → `new Function()`」——
|
|||
|
|
**跳过了模板求值这一步**,所以是**假绿**。浏览器里 `window.__dshRecover` 为 `false`、注入 CSS 长度 0,才是真相。
|
|||
|
|
|
|||
|
|
**顺带挖出的第二个(更早存在、更严重的)缺陷**:同一文件里的 `SESSION_ASSIST_JS`(档案 56 的
|
|||
|
|
「我的文件 / 能力」助手)**一直是坏的** —— 里面 8 处 `'\n'` 同样是单反斜杠写法 ⇒ 模板求值后裸换行 ⇒
|
|||
|
|
**该脚本从来没在浏览器里跑过**。此前"验证"只 grep 了页面 HTML 里有没有 `__dshAssist` 字样,
|
|||
|
|
**没有验"脚本跑不跑得起来"**(同一个假绿模式)。两个脚本今天一起修好。
|
|||
|
|
|
|||
|
|
**修复**:
|
|||
|
|
1. 我引入的那处 → `.join(String.fromCharCode(10))`(不依赖任何转义);
|
|||
|
|
2. 助手脚本的 8 处 → 按源文件补成 `\\n`(只补未转义的,已转义的保持原样);
|
|||
|
|
3. ⛔ **新增常设校验 `scripts/verify-inject.cjs`**(已接入 `npm test`):**先把模板字面量求值成运行时字符串,再 `node --check`**,
|
|||
|
|
并断言运行时串里不含反引号 / `${`。⇒ 这类"假绿"再也不可能溜过 `npm test`。
|
|||
|
|
|
|||
|
|
**同时补齐的触发面(这才是"页面没反应"的另一半)**:实测(见下)确认——**用户一直盯着页面、连接悄悄断掉时,原设计一个事件都不会来**(只有切标签/切回来、focus、pageshow 才探活),所以既不提示也不恢复。
|
|||
|
|
- **① 心跳**:页面**可见**时每 25 秒静默探一次(不可见时零成本);
|
|||
|
|
- **② 流断**:包装 `EventSource` / `WebSocket` 的 `error` / `close`,立即探一次(比心跳更早);
|
|||
|
|
- 两者都走新的 **soft 模式:连续两次失败才恢复** —— 因为恢复 = 原地 `location.replace`,**页面内未保存的输入会丢**,一次网络抖动不该触发它。
|
|||
|
|
- ⚠️ **这推翻了本文 §五 方案 B「不做周期性探活」**:当时的前提是"用户在场却不操作时毫无收益",**现场证明该前提不成立**。代价是每个可见页面每 25 秒 2 个轻请求(`/api/dsh/status` + 实例侧 `GET /`),量级可忽略。
|
|||
|
|
|
|||
|
|
**验证(真 Chrome + 临时会话,全部只读)**:
|
|||
|
|
|
|||
|
|
| 项 | 结果 |
|
|||
|
|
|---|---|
|
|||
|
|
| 修复后基线 | ✅ `window.__dshRecover = true`、`window.__dshAssist = true`、**语法类错误 0**(助手脚本首次真正运行) |
|
|||
|
|
| 事件路径回归 | ✅ `pageshow(persisted)` → 浮层出现、`class=__dsh-ov`、新视觉节点(orb/track)齐 |
|
|||
|
|
| **无操作自动恢复(本次核心验收)** | ✅ 只打挂实例侧探针、**全程不派发任何事件** → **47.4 秒后浮层自动出现**(= 两次心跳判定),文案「工作区正在恢复,请稍候…」,0 页面错误 |
|
|||
|
|
| `npm test` | ✅ 45 pass / 0 fail / 1 skipped,**且新增的注入脚本运行时校验双脚本通过** |
|
|||
|
|
| 部署 | ✅ 两轮热修(PID 415625→416910→**417427**),备份 `proxy.ts.bak-before-hotfix-0825` / `proxy.ts.bak-before-triggers-0835` |
|
|||
|
|
|
|||
|
|
**教训(已写进 `MEMORY.md` 与技能)**:**模板字面量里的转义会先被求值一次** ——
|
|||
|
|
凡"把 JS 塞进模板字面量"的注入脚本,校验必须**先求值再验**,否则所有 grep/`new Function(原文)` 都是假绿。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 追加(2026-09-13 10:0x · 视觉升级续):**导航过渡页 `wake.html` 也统一成「AI 核心」**
|
|||
|
|
|
|||
|
|
**触发**:用户报「admin(Edge)能自动弹浮层恢复,guest(Chrome)不行」→ 实测(真 Chrome 无头 + guest 临时会话)**guest 侧脚本、心跳、浮层全部正常**(46.2 s 自动出现)⇒ 差异不在浏览器,而在**落在了哪个界面**:
|
|||
|
|
|
|||
|
|
| 界面 | 有无注入脚本 | 加载态视觉 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| **实例页** | ✅ 有 | 「AI 核心」浮层(档案 77 本次升级) |
|
|||
|
|
| **过渡页 `wake.html`**(平台侧;刷新/首次进入且实例不在时落这里) | ❌ 无(`__dshRecover` 命中 0) | **仍是旧的 `border-top` 转圈** |
|
|||
|
|
|
|||
|
|
⇒ guest 实例今早崩过 5 次并被回滚,刷新时**多半落在过渡页**,所以"看不到浮层"。**这是结构使然,不是 bug**,但两套视觉确实割裂。
|
|||
|
|
|
|||
|
|
**改动**:`web/wake.html` 的加载态换成与浮层**同一套视觉语言**(conic 扫描弧 + 反向虚线环 + 脉冲核心 + 三轨道粒子 + 渐变进度条 + 等宽小字标签),**配色跟随平台主题**(`#534AB7` / `#8B7CF6`,不硬套暗色)。
|
|||
|
|
|
|||
|
|
**三条纪律**:
|
|||
|
|
1. **JS 逻辑一行未动**(diff 只差档案 78 加的那段 `instance_circuit_open` 分支);
|
|||
|
|
2. **行尾保持 LF**(本机 CRLF=0,与文件一致);
|
|||
|
|
3. **静态页免重启** ⇒ scp 即生效(比浮层省一次重启窗口)。
|
|||
|
|
|
|||
|
|
**验证**:线上 `https://alotbuy.com/wake.html` 取回即为新版(`wk-orb` / `conic-gradient` / `prefers-reduced-motion` / `dsh · auto recovery` 命中,旧 `.wk-spin{width:34px…}` 已消失);截图 `_patch77/shots/wake-aicore.png`。回滚点 `/opt/dsh/backups/wake.html.bak-aicore-20260913`。
|
|||
|
|
|
|||
|
|
> **留在档案里的诊断口诀**:客户报"看不到浮层/没反应"时先分清是**哪个界面** ——
|
|||
|
|
> 实例页(有脚本,出浮层)还是过渡页(平台侧,只有加载态);再看该标签页是不是**修复前打开的旧页面**(那种情况刷新一次即可)。
|