Skip to content

fix(app): 同一帳號的第二台裝置永遠不知道有進行中的行程(第二十八輪) - #100

Merged
thothawei merged 1 commit into
mainfrom
claude/round28-multi-device
Aug 1, 2026
Merged

fix(app): 同一帳號的第二台裝置永遠不知道有進行中的行程(第二十八輪)#100
thothawei merged 1 commit into
mainfrom
claude/round28-multi-device

Conversation

@thothawei

Copy link
Copy Markdown
Owner

先收上一輪的尾巴,再開新族。

一、收尾:司機端的階段 default: 是乾淨的——但理由不是「App 有防禦」

司機端 ActiveRide.fromBackendJson 的 phase 是 status == pickedUp ? onTrip : enRouteToPickup,看起來跟乘客端一樣有「未知碼落到某個分支」的風險。但後端擋在前面FindActiveByDriver 的 SQL 是 status IN (accepted, pickedUp)白名單

這是後端契約保證,不是 App 的防禦——所以要記住:哪天後端把新狀態加進那個白名單,App 這裡就要跟著改。已寫進 TODO。

二、本輪的族:多裝置同一帳號

根因一句話

_handleWsEvent 對「不認得的行程事件」是 if (active == null || event.rideId != active.rideId) return;——直接丟掉;而輪詢在「沒有進行中訂單」時是停著的_applyActiveRide_stopPolling)。兩件事湊起來,第二台裝置就永遠不會知道有一趟車正在跑。

這是現在就會踩到的,不需要後端改任何東西

  1. 同一個乘客帳號登入兩台裝置(手機+平板,或舊手機還登著);
  2. B 台開著、停在叫車表單;
  3. A 台叫車、司機接單——後端的 WS Hub 依 (角色, id) 扇出,B 台收得到 ride.accepted
  4. B 台把它丟掉 → 畫面永遠是叫車表單,按下去只會拿到「已有進行中訂單」。

司機端早就處理過同一件事DriverControllerrideAccepted:另一台裝置接走了這張單 → 重讀 active,註解寫明已對真後端實測)。乘客端從來沒有做過同一件事。

三、修法與三條邊界

決定 為什麼
不認得的行程事件 → refreshActive(silent: true) 對完帳就自己接回輪詢,畫面跟上
只在 activeRide == null 時對帳 已有一張單卻收到別的 rideId = 舊單的落隊事件(乘客一次只有一張進行中訂單)。這條是既有測試逼出來的——第一版沒收窄,customer_cancel_notice_test 的「別的行程的取消事件不觸發通知」立刻紅
driver.location 不觸發 每幾秒一則的串流,會把一次對帳變成連續打點。真正要跟上的是生命週期事件,而那些一定會跟著來

四、驗收

  • flutter analyze 無 issue、flutter test 444 passed(440 +新 4)、57 個測試檔。
  • 先紅再修:新測試在修改前就是紅的(activeCalls 停在 1,事件被丟掉)。
  • 反向驗證三個半邊各一次
    • 拿掉對帳 → 主案 FAIL
    • 拿掉 driver.location 排除 → 「不觸發連續打點」那案 FAIL
    • 拿掉 active == null 收窄 → 本檔新案與既有的 customer_cancel_notice_test 各紅一案
  • flutter build apk --debug --flavor customer -t lib/main_customer.dart 成功。

五、過程中修正的一個自己的錯誤假設

第一版測試斷言「認得的單不該多打 API」——跑出來是紅的,而錯的是我不是程式rideAccepted 本來就會 refreshActive(silent: true) 補齊完整資料。已把那一案改成守「沒有把已知單的路徑改壞,尤其不能變成兩次補讀」。

六、誠實說明沒做到的

WS 斷線期間發生的事:B 台若在事件送出的當下沒有連線,這次對帳就不會發生。目前靠 onAppResumed 的無條件 refreshActive 自癒——但那需要 App 進過背景再回來,一台一直開在前景的裝置不會走到。要修的話動的是 onConnectionChanged(WS 重連成功後補一次對帳),已列進下一輪候選。

開工前的例行實查(第十一次)

三個外部卡點依然都不在;開工時三個 repo 的 open PR 皆為 0。

🤖 Generated with Claude Code

先收上一輪的尾巴:司機端的階段 default **是乾淨的**,但理由不是「App 有防禦」——
後端 FindActiveByDriver 的 SQL 是 status IN (accepted, pickedUp) 白名單,
所以司機端只可能收到這兩種狀態。這是後端契約保證,哪天白名單加了新狀態,
App 這裡要跟著改(已寫進 TODO)。

本輪的洞:_handleWsEvent 對「不認得的行程事件」直接 return,而輪詢在「沒有進行中
訂單」時是停著的(_applyActiveRide 會 _stopPolling)。兩件事湊起來,第二台裝置
永遠不會知道有一趟車正在跑。

這是現在就會踩到的,不需要後端改任何東西:同一帳號登入兩台裝置 → B 台停在叫車
表單 → A 台叫車、司機接單 → 後端 WS Hub 依 (角色, id) 扇出,B 台收得到
ride.accepted → 被丟掉 → B 台永遠是叫車表單,按下去只拿到「已有進行中訂單」。
司機端早就處理過同一件事(DriverController 的 rideAccepted 註解寫明已對真後端實測),
乘客端從來沒有做過。

修法與三條邊界:不認得的行程事件去跟後端對一次帳;**只在 activeRide == null 時**
對帳(已有一張單卻收到別的 rideId = 舊單落隊事件,照舊忽略——這條是既有測試
customer_cancel_notice_test 逼出來的,第一版沒收窄它立刻紅);driver.location
不觸發(每幾秒一則的串流,會把一次對帳變成連續打點)。

驗收:flutter analyze 無 issue、flutter test 444 passed(440+新 4)、57 個測試檔、
flutter build apk --debug --flavor customer 成功。新測試在修改前就是紅的。
反向驗證三個半邊各一次:拿掉對帳 → 主案 FAIL;拿掉 driver.location 排除 → 連續
打點那案 FAIL;拿掉 active == null 收窄 → 本檔新案與既有的 cancel_notice 各紅一案。

過程中修正一個自己的錯誤假設:第一版測試斷言「認得的單不該多打 API」,跑出來紅的,
而錯的是我不是程式——rideAccepted 本來就會 refreshActive 補齊完整資料。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@thothawei
thothawei merged commit 2d8ffc7 into main Aug 1, 2026
2 checks passed
@thothawei
thothawei deleted the claude/round28-multi-device branch August 1, 2026 12:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant