實戰範例 02:修復登入超時無提示錯誤
修復當後端 Session 或 Token 超時後,使用者在前端操作無任何提示、介面卡死,且未自動跳轉至登入頁的體驗錯誤。
/opsx:explore/opsx:propose/opsx:apply/opsx:verify/opsx:archive 實戰背景
在許多 Web 應用程式中,當使用者閒置時間過長,後端所核發的驗證權杖 (Token) 會超時失效。如果前端沒有妥善處理 API 請求回傳的 401 Unauthorized 錯誤,就會導致使用者點擊任何按鈕皆「無反應」,畫面陷入卡死狀態。
本範例中,我們將透過調查並修改 API 攔截器,實作當偵測到 Token 超時時,主動彈出倒數提示視窗 (Modal) 並安全重導向至登入頁的功能。我們將使用 完整嚴格工作流 (Full Flow) 來完成此項任務。
規格定義
本次變更計畫的 Capability 為 user-authentication。我們預計在規格增量 (Spec Delta) 中定義以下驗收標準:
## MODIFIED Requirements
### Requirement: Session timeout handling
The site SHALL intercept token expiry errors and gracefully guide the user to re-authenticate.
#### Scenario: Unauthorized response redirect
- **WHEN** any API call returns a 401 response status due to token expiry
- **THEN** the site SHALL show a Session Timeout Modal and redirect to `/login` after 3 seconds
端到端 6 輪 AI 協作紀錄
以下是 You 與 AI 助手的完整對話歷史。
── 第 1 輪:探索與診斷 codebase ──
- You:
/opsx:explore "使用者說登入久了點按鈕會沒反應,API 回傳 401 但網頁沒動靜。幫我查一下我們專案的 API 請求是怎麼發送的。" - AI:
好的,已進入探索模式。我掃描了專案的 codebase:
- 我們的 API 呼叫主要封裝在
src/utils/apiClient.ts中,使用 Axios 套件。 - 檢視
apiClient.ts的內容,發現它只設定了request攔截器用來附加 Token,但是完全沒有設定response錯誤攔截器。
這是造成該 Bug 的根源:後端回傳 401 錯誤後,Axios 拋出 Promise Reject,但前端代碼中許多地方沒有寫
catch邏輯,導致錯誤被 Swallow,使用者介面無任何回饋。建議方案: 我們應該在
apiClient.ts加上response攔截器,統一捕捉 401 錯誤,彈出 Modal 提示使用者「登入已超時」,並在 3 秒後自動導向/login。如果您同意此方案,請退出探索模式並建立變更計畫。
- 我們的 API 呼叫主要封裝在
── 第 2 輪:建立提案 ──
- You:
/opsx:propose "修復登入超時無提示的 Bug,攔截 401 錯誤並彈出 Modal 3 秒後跳轉" - AI:
好的,已為您建立變更計畫
fix-session-timeout-bug:- 建立 proposal.md: 說明 Why 與 What。
- 建立 specs/user-authentication/spec.md: 新增超時處理與 401 跳轉的驗收場景 (Scenario)。
- 建立 tasks.md: 包含以下實作任務:
- 1.1 在 Axios 攔截器中捕捉 401 狀態碼
- 1.2 建立全域超時提示 Modal 元件
- 1.3 串接全域狀態與重導向邏輯
- 1.4 撰寫模擬 401 錯誤的測試案例
規劃就緒!請執行
/opsx:apply開始實作。
── 第 3 輪:實作任務 1 (Axios 攔截器修改) ──
- You:
/opsx:apply - AI:
Implementing: fix-session-timeout-bug (schema: spec-driven)
正在處理任務 1/4:在 Axios 攔截器中捕捉 401 狀態碼。
我已修改了
src/utils/apiClient.ts,加入錯誤響應響應 (Response Error) 攔截器:import axios from 'axios'; import { emitter } from './eventEmitter'; export const apiClient = axios.create({ baseURL: '/api' }); apiClient.interceptors.response.use( (response) => response, (error) => { if (error.response && error.response.status === 401) { // 發布全域事件「登入已超時」 emitter.emit('session-timeout'); } return Promise.reject(error); } );✓ 任務已完成。已將任務 1.1 標記為完成。
準備好進入任務 1.2「建立超時提示 Modal」了嗎?
── 第 4 輪:實作任務 2 (建立超時 Modal) ──
- You:
繼續建立 Modal 元件。 - AI:
Implementing: fix-session-timeout-bug
正在處理任務 2/4:建立全域超時提示 Modal 元件。
我已在
src/components/TimeoutModal.tsx實作了 React 視窗元件。外觀採用乾淨的單欄排版、紫色按鈕、以及 3 秒倒數計時顯示。import React, { useEffect, useState } from 'react'; export const TimeoutModal = () => { const [isOpen, setIsOpen] = useState(false); const [countdown, setCountdown] = useState(3); ... };✓ 任務已完成。已將任務 1.2 標記為完成。
下一步將實作全域狀態的訂閱與跳轉邏輯。是否繼續?
── 第 5 輪:實作任務 3 (整合狀態與跳轉) ──
- You:
繼續整合跳轉邏輯。 - AI:
Implementing: fix-session-timeout-bug
正在處理任務 3/4:串接全域狀態與重導向邏輯。
我已將
TimeoutModal.tsx整合入全域 Layout 中,並使用 Event Emitter 訂閱session-timeout事件:useEffect(() => { const handleTimeout = () => { setIsOpen(true); const timer = setInterval(() => { setCountdown((prev) => { if (prev <= 1) { clearInterval(timer); window.location.href = '/login'; // 跳轉至登入頁 } return prev - 1; }); }, 1000); }; emitter.on('session-timeout', handleTimeout); return () => emitter.off('session-timeout', handleTimeout); , []);✓ 任務已完成。已將任務 1.3 標記為完成。
剩下最後的測試任務。準備好要執行了嗎?
── 第 6 輪:撰寫測試、驗證與歸檔 ──
- You:
撰寫測試,然後執行 verify 並歸檔。 - AI:
Implementing: fix-session-timeout-bug
正在處理任務 4/4:撰寫模擬 401 錯誤的測試案例。
我已在
tests/apiClient.test.ts中使用 mock server 模擬 401 Unauthorized 響應,並成功通過測試,驗證了session-timeout事件被觸發且 modal 能正確計時。✓ 4/4 任務全數完成。
執行自動驗證與歸檔:
- 驗證成功:實作細節符合
Scenario: Unauthorized response redirect的要求。 - 智慧同步:已將變更合併至主規格 openspec/specs/user-authentication/spec.md。
- 目錄歸檔:已移動計畫資料夾至
openspec/changes/archive/2026-07-14-fix-session-timeout-bug/。
Archive Complete
變更計畫已成功關閉!工作目錄目前非常乾淨。
- 驗證成功:實作細節符合
程式碼與設計亮點
- 事件驅動解耦:使用事件訂閱機制 (Event Emitter) 讓 API 客戶端與 UI 視窗解耦,API 客戶端無需直接依賴 React 組件。
- 倒數優雅跳轉:給予使用者 3 秒的緩衝提示,明確告知「登入已超時」才執行跳轉,避免突然的頁面跳轉造成困惑。
- 寬容退路 (Fallback):若瀏覽器不支援某些 API,則直接降級 (Graceful Degradation) 執行立即重導向。