我先把 Phase 1 落地了，重点做了仪表盘自动刷新和节点实时连接兜底，并把计划文档同步更新了。

- `vite-frontend/src/pages/dashboard/use-dashboard-data.ts:55` 现在加入了 5 秒静默轮询、标签页可见性恢复刷新，以及更稳定的加载回调拆分，避免每次刷新都走整套初始化逻辑。
- `vite-frontend/src/pages/dashboard/use-dashboard-data.ts:82` 把过期提醒的去重 key 改成基于用户/隧道到期信息的稳定标识，轮询刷新流量时不会反复弹 toast。
- `vite-frontend/src/pages/node/use-node-realtime.ts:13` 补强了 WebSocket 重连策略：前几次按递增延迟重连，超过阈值后进入长间隔 fallback 模式，而不是彻底停掉。
- `vite-frontend/src/pages/node.tsx:68` 增加了节点列表 merge 逻辑；fallback 刷新时会保留已有 `systemInfo`、升级/复制/回退 loading 状态，避免列表刷新把实时信息清空。
- `vite-frontend/src/pages/node.tsx:1160` 节点页断线提示现在会区分“正在重连”和“已切换为列表自动刷新兜底模式”。
- `vite-frontend/src/vite-env.d.ts:4` 补上了 `VITE_API_BASE` 类型声明，顺手消掉这次构建会碰到的环境变量类型报错。
- `plans/020-ajax-no-refresh-ux.md:19` 已把本次完成的 3 个 Phase 1 checklist 勾掉。

验证情况：
- 运行了 `cd vite-frontend && npm install`
- 运行了 `cd vite-frontend && npm run build`
- 前端构建已通过

这次额外生成了依赖文件：
- `vite-frontend/package-lock.json`

下一步自然就是继续 Phase 2，把 `forward` / `tunnel` / `user` 的“操作后整页数据重拉”改成局部 patch。你要我继续的话，我建议从 `vite-frontend/src/pages/forward.tsx` 开始，因为它已经有一部分本地更新模式，改起来最顺。