title: [ISSUE-0002] Popup 启动后 loading 骨架屏永久卡住 description: "一直卡在这(截图:popup 顶部骨架屏一直转),如何登录呢?" tags: [issue, auth] created: 2026-05-26 updated: 2026-05-26 type: issue status: fixed severity: critical
[ISSUE-0002] Popup 启动后 loading 骨架屏永久卡住¶
相关 wiki:
popup-auth-state-machine.md
用户感知的现象¶
"一直卡在这(截图:popup 顶部骨架屏一直转),如何登录呢?"
打开扩展 popup → 顶部第一块永远显示骨架屏 + 转圈 + 没有任何登录入口 → 用户无法操作。
根因分析¶
src/sections/popup/index.tsx v0.10.7 引入的 3 态状态机有死循环:
type AuthState = 'loading' | 'logged-in' | 'logged-out';
useEffect(() => {
storage.getItem('local:uid').then((storedUid) => {
if (!storedUid) setAuthState('logged-out');
// else 留在 loading,等 user 数据返回 ← 死等!
});
}, []);
useEffect(() => { if (uid) setAuthState('logged-in'); }, [uid]);
- storage 有旧 uid(之前登过留的)
useAuthContext拉 user 失败(token 过期 / API 挂 / 网络问题)uid永远是 undefined- → authState 永远是
loading - → popup 一直显示骨架屏,无登录按钮
修复方案¶
1. 加第 4 态 stale + 2 秒超时兜底¶
type AuthState = 'loading' | 'logged-in' | 'logged-out' | 'stale';
useEffect(() => {
storage.getItem('local:uid').then((storedUid) => {
if (!storedUid) { setAuthState('logged-out'); return; }
// 给 user 2 秒;超时切 stale
timeoutId = setTimeout(() => {
setAuthState((s) => (s === 'loading' ? 'stale' : s));
}, 2000);
});
}, []);
2. loading 也提供「手动登录」入口¶
骨架屏右侧加小按钮,2 秒等不了的用户能立即操作。
3. 统一 handleLoginClick¶
先 try-cookie-login(force=true),成功 reload;失败开窗 + 关 popup。
改动文件¶
| 文件 | 改了什么 |
|---|---|
src/sections/popup/index.tsx |
4 态状态机 + 2s 超时 + handleLoginClick |
验证方式¶
- 已登录 → 打开 popup → loading 1s 内变 logged-in
- 从未登录 → 打开 popup → loading 1s 内变 logged-out,显示登录按钮
- stale(手动清空 user API 响应)→ 打开 popup → 2s 后变 stale,显示橙色「会话失效·重新登录」
- 网络挂 → 打开 popup → 2s 后切 stale,可点击手动登录
如何避免再犯¶
- 任何"等异步结果"的 loading 状态必须有超时(2 秒是经验值)
- loading 状态不要隐藏所有操作入口 — 给用户主动逃出 loading 的机会
- stale ≠ logged-out:UI 区分有意义,重新登录路径相同但心理预期不同
相关问题¶
- ISSUE-0001 — 同源问题(stale token),background 那层 v0.10.17 才修