async-task-uilisted
Install: claude install-skill poloplay0114/hard-won-claude-skills
# 背景任務與多工介面紀律(Async Task UI)
## 什麼時候用
任何時候介面上出現「開始」按鈕、而工作要跑超過幾秒:報表/文件產出、批次匯入、長時間查詢、
AI 生成、檔案轉換。這類介面的物理是固定的:**HTTP 是無狀態的、前端是會死的(重整/關頁/斷線)、
使用者是會亂點的**——而任務必須活得比這三者都久。業界對此有一套高度一致的標準骨架
(送出→立即回任務 ID→背景執行→狀態端點輪詢→終態→領取結果);這份技能是那套骨架
加上真實踩雷換來的鐵則。失敗模式極其固定,標準解也是——不需要發明,需要的是不偏離。
---
## 核心法則
### 法則 1:狀態機先於介面——先定生死,再畫畫面
動手寫任何任務 UI 元件之前,先把任務狀態機定下來:最小集
`queued(排隊)→ running(執行中)→ succeeded / failed / cancelled(終態)`,
外加正交的 `delivered(已領取)`旗標。
- **狀態轉移寫成一張表入碼**(哪個狀態能到哪個狀態),禁止散落在各處的 if;
轉移表配測試——非法轉移會吠。
- 進度百分比、預估時間都是裝飾,**狀態才是骨架**;先有誠實的狀態,才配有進度條
(給不出誠實的百分比就別給,狀態文字比假進度可信)。
- 沒先定狀態機就寫介面=每個元件各自發明任務的生死,之後每顆 bug 都是「兩處對任務
狀態的理解不一致」。
### 法則 2:終態不可逆——完成的任務進收件匣,絕不還魂
終態(succeeded/failed/cancelled)是單行道:**任何自動機制(重連、輪詢、恢復)
永不把終態任務重新開成工作視圖**。
- 完成而未領取(succeeded 且未 delivered)的任務 → **收件匣/任務中心**:被動列出,
使用者點了才開;絕不自動佔用分頁/版面。
- 歷史殘留任務(幾天前的、測試留下的)一律歸收件匣;「載入時把所有找得到的任務
都開成分頁」是本技能最經典的案發現場(見下)。
- 收件匣同時解掉「任務完成時使用者不在場」的交付問題——結果有家可歸,不必賭
使用者的視窗還開著。
### 法則 3:視圖與���務分離——伺服器是真相源,前端只是望遠鏡
任務的生死存亡**絕不繫於任何視窗/分頁/前端物件的存活**:
- 送出後任務屬於伺服器;前端拿 task_id 輪詢狀態端點——關頁、重整、斷線,任務照跑。
- **重整=望遠鏡重新對焦**:載入時查詢「進行中/排隊中」任務、把視圖掛回去;
不是任務重生、更不是任務死亡。
- 重連機制只認**非終態**(法則 2);且「掛回」=更新既有視圖或掛回既有容器,
**絕不因輪詢撈到東西就開新版面**——視圖的誕生只有兩個合法來源:使用者動作、
載入時掛回真正在跑的任務。
- 多視圖(多分頁)時,每個視圖各持自己的狀態容器(per-tab context),
禁全域單例——全域狀態跨 await 交換必有 re-entrancy 撞況。
### 法則 4:冪等是送出的義務——連點、重試、重載都不得生出第二份工作
背景工作天生會被觸發多次(連點、網路重試、頁面重載後再按)——**同一份邏輯工作,
跑幾次都只能算一次**:
- 前端:送出即禁用按鈕/去抖(第一道,擋手速)。
- 服務端:冪等鍵(內容鍵或請求鍵)——同鍵重複送達=回同一個 task_id,不開新任務
(第二道,擋一切前端擋不住的)。兩道都要,只有前端的=沒有。
-