17 Commits

Author SHA1 Message Date
0010cc35c3 feat(workspace): canvas overlay 疊 bbox + WS 即時推論面板(前端塊2)
推論工作區前端塊2:canvas overlay 疊在 MJPEG <img> 上、接 WS
inference:<deviceId> 推的 raw InferenceResult、即時繪 bbox+label+信心度;
右側 Inference panel 從 Phase 1 佔位換成 WS 即時結果清單 + FPS/延遲。
照 edge-ai-platform POC 移植(唯讀參考)。

- camera-overlay.tsx:normalized bbox→canvas 像素換算、label 框
- inference-panel.tsx:指標 + 清單 + aria-live 無障礙
- inference-store.ts:fps/avgLatency、MAX_RESULTS=100 上限
- use-inference-stream.ts:用既有 useWebSocket(same-origin cookie、無 token URL)
- workspace-client:overlay 塞 CameraFeed slot、訂閱 WS(僅 isRunning && isOnline)

canvas 顏色讀設計 token(--chart-*/--background,getComputedStyle 快取、
主題切換才重讀)、跟隨深淺主題、不裸色值。

Reviewer 2 輪通過(0C/0M)。修正:
- M1 overlay 座標錯位:feedSize 初值 null、未拿真實顯示尺寸不繪(MJPEG 非
  4:3 不錯位)
- M2 效能 jank:palette 快取 useState + MutationObserver 主題切換才重讀
  (不再每 frame getComputedStyle)
- m2 label fallback 色改實際色(canvas 不認 currentColor)
- m3 isValidBBox guard:壞 payload 略過不 crash

+6 新測試(M1 640×360→height=360、M2 呼叫數不增、m3 NaN/undefined 略過)。
tsc/eslint clean、塊2 範圍 37 test 綠。(全套 11 failed 全屬 conversion-store
既有 time-based flaky、未觸碰)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 05:03:35 +08:00
ba8fd454ea feat(workspace): Camera tab 接真 MJPEG stream + start/stop 狀態機(前端塊1)
推論工作區前端塊1:Camera tab 從 placeholder 換成真正接 MJPEG stream
的即時畫面。照 edge-ai-platform POC camera-feed.tsx 移植(唯讀參考、
重寫為 visionA 慣例)。

- camera-feed.tsx:MJPEG <img> + ResizeObserver + overlay slot(塊2 用)+
  角落標籤
- camera.ts buildStreamUrl:cache-bust 走 _t= query(encode)、token 絕不
  放 URL(same-origin cookie 認證、對齊 use-websocket §10)
- workspace-client Camera tab:接 CameraFeed + 串流狀態機、掉線清串流採
  render 期衍生(非 effect setState)
- use-websocket.ts:修 same-origin WS URL bug——getWsBaseUrl 回 "" 時
  new WebSocket() 需絕對 URL,補 window.location wss/ws(塊2 WS 用)
- types/camera.ts + types/inference.ts(後者鏡射 local-tool driver.InferenceResult)
- start/stop 契約改打 /api/camera/start|stop
- i18n 9 key 雙語齊全

Reviewer 0C/0M 通過。13 test(camera-feed 8 + camera 5)PASS、tsc/eslint clean。
(全套 11 failed 全在 conversion-store.test.ts 既有 flaky、本次未觸碰、經 grep 證實無關)

follow-up(交塊2/3):掉線→回線 stream 卡舊 URL,建議掉線一併 clearStream。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 04:42:41 +08:00
c0e2ac0a59 fix(devices): 前端讀 tunnel_online 修雲端裝置恆離線
normalizeDevice 原本只讀 remote_status(DB 靜態值、exchange 建 device
寫死 offline 永不更新),沒讀後端即時算出的 tunnel_online → tunnel 已通
但雲端裝置列表恆顯示離線。

改為:tunnel_online === true 時 remoteStatus 覆蓋為 online,否則 fallback
回原本 remote_status 判定,兩者皆缺維持 unknown。嚴格布林比對避免 truthy
誤判。多裝置誤判限制(後端 resolveTunnelStatus 寬鬆比對、正解 R3/Phase 1)
已在 code 註解標明,不在本次前端修法範圍。

新增 4 test(true→online / false→沿用 / 缺欄位→沿用 / 皆缺→unknown),
共 16 test 全綠、tsc/eslint 乾淨。Reviewer 0C/0M。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 06:46:44 +08:00
ab50691f8a fix(conversion): 轉檔完成頁「已加入」按鈕不再重複 promote
SuccessView「加到模型庫」按鈕文字依 importedModelId 切換(已加入/加到模型庫),
但 onClick 永遠是 handleImportClick(開 PromoteDialog)→ 已 promote 後再點又
開 dialog 又 promote。

- 按鈕 onClick 改 handleImportButtonClick 分流:importedModelId 有值→
  router.push(/models/{id}) 導航(不再 promote);空→開 PromoteDialog(現狀)
- aria-label 同步切換(import.aria.done)對齊實際行為
- 後端 PromoteToModels 本有冪等(同 jobID 不重複建),前端 onClick 為防禦層

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 00:30:18 +08:00
7d8ad4857e fix(dashboard): 修首頁白屏(React #185 無限 re-render)
connected-devices-list 的 selector 內 .filter() 每次回新陣列,zustand v5
移除淺比較後 Object.is 永 false → 無限 re-render → #185 整頁白屏(配對後
有 device 資料才觸發)。

- selector 改回只取 s.devices(穩定 ref),filter 移到 component body
- activity-timeline 未知 type 加 fallback(?? Circle / muted)防 F7/F8 WS 事件 crash
- 測試 render not.toThrow + act flush 驗不再迴圈

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 00:17:38 +08:00
6de7c1b4a3 feat(devices): 裝置詳細頁加移除(unpair)功能
- device-store 加 unpairDevice action(接 POST /api/devices/:id/unpair)+ unpairingId
- 詳細頁加移除按鈕 + AlertDialog 二次確認(說明解除配對需重新配對、不可逆)
- 成功 toast + 導回 /devices;403/404/unknown 分流 toast、失敗不導頁
- i18n devices.remove.* 雙語

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 00:17:37 +08:00
0901ffafda feat(models): 模型庫頁按來源分三區(B2)+ 來源標籤 chart token 上色
- /models 改三區渲染:預設模型/我轉檔的/我上傳的(groupModelsBySource)
- 移除 source 篩選器(三區已分來源)、保留 targetChip 篩選跨三區
- 空區顯示標題+空狀態;新 model-section 分區元件
- 來源標籤三色:preset=chart-1/converted=chart-2/uploaded=chart-3
  (設計系統 token、badge + 區標題色點呼應、dark mode 自動跟隨)
- i18n models.section.* + .empty.* 雙語

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 00:17:19 +08:00
9ab1a11ea4 feat(models): 補 7 個預設模型(B8)+ 詳細頁下載按鈕(B5)
# B8 預設模型(簡單版:寫死常數 + 打包 + 不簽 token)
模型庫「預設模型」原本實際是空的(舊 seed 是 demo 假資料、production 不啟用)。
補 7 個公用預設模型(kl520×4 + kl720×3,來源 local-tool models.json):
- metadata 寫死成 Go 常數(presets.go),不進 DB → 公用、不可刪、無 seed 重複
- 7 個 .nef 打包進 image(assets/preset-models/,~61MB;Dockerfile COPY)
- download 走 visionA 自己(簡單版不簽 token,preset 公用本不需授權):
  download handler 三分支 preset→visionA URL / converted→FAA / uploaded→501
- 新 GET /preset-models/*filepath 靜態 serve(無 auth、Content-Disposition
  attachment 用 mime.FormatMediaType、path-traversal 防禦)
- list/get 含 preset(對所有人可見)

# B5 詳細頁下載按鈕 + preset 可下載
- model-detail-client.tsx 補下載按鈕(行為對齊列表卡片)
- isModelDownloadable 加 preset(source==="preset" → true)
- normalizeModelSummary 對 target_chip toLowerCase(修 preset 大寫 KL520
  在小寫晶片篩選下被隱藏的 bug;收斂所有來源大小寫)

# 測試
backend:presets/preset 下載三分支/靜態 serve/path-traversal/條件對稱 全綠
frontend:詳細頁下載鈕 + preset 可下載 + target_chip 大小寫篩選回歸 38 PASS
Reviewer 兩輪通過(Major-1 + Minor-1/2 修畢、複審 0 問題)

# backlog(未做的優化)
preset 下載改 HMAC presigned / preset .nef 改放 FAA 減 image / metadata 後台可管理

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 07:38:25 +08:00
3e45532f55 fix(model-download): 改 redirect/query-string token 下載,根除 CORS preflight 405
模型庫下載原本前端用 fetch + Authorization: Bearer 跨 origin 直連 FAA,
觸發 CORS preflight(OPTIONS);FAA 未設 CORS、OPTIONS 回 405 → 下載失敗。

FAA 設計本就支援「token 放 query string(access_token) + redirect 導航下載」,
故不需 FAA 設 CORS。改採 ADR-017 §11 v1.3 乙案:

- backend: download_url 組成含 ?access_token={url.QueryEscape(token)} 完整 FAA URL;
  ModelDownloadResponse.Token 標 deprecated 保留(向下相容);token 不進 log
- frontend: 移除 downloadModelFile/deriveDownloadFilename(fetch+blob),
  改 triggerNavDownload 用 <a download href> 導航;ModelDownloadGrant 移除 token 欄
- 整條鏈無 CORS:前端→backend 同 origin、前端→FAA 導航無 preflight

docs: ADR-017 增補 §11(v1.3) + TDD.md §9.5
tests: backend 8 PASS(含 token escape 邊界)、frontend 35 PASS;Reviewer 通過(0C/0M)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 06:18:05 +08:00
c2f0b1549e feat: OIDC 登入修復(email fallback / prompt=login / logout 連動)+ 真轉檔鏈路 e2e
接 DB 後真人 OIDC 登入暴露 MC OIDC provider 實作不完整,visionA 端逐項繞過,
讓登入/換帳號可用;另補真轉檔服務的整合 e2e。

OIDC 登入修復(MC 端根因另有交接檔,visionA 先繞過):
- email fallback:MC id_token 不發 email claim(ASP.NET Identity 預設 factory 只發
  sub/name)→ A7 email 必填擋住登入。callback email 空時用 <sub>@noemail.visiona.local
  placeholder,不污染 schema,MC 修好發真 email 後 ON CONFLICT 自動覆寫
- prompt=login:authorize 帶 prompt=login(config VISIONA_OIDC_PROMPT_LOGIN,預設關)
- logout 連動 MC:logout 回 idp_logout(MC Web :7880 /account/logout,GET),前端用
  隱藏 iframe 觸發清 MC session(Web/Api 共享 DataProtection)→ 能換帳號。
  config VISIONA_OIDC_LOGOUT_URL、向下相容(未設則只清本地)

真轉檔鏈路 e2e(//go:build realconv,按需對 stage 跑、不污染主測試集):
- real_converter_e2e:give 真轉檔服務 contract(init→poll→completed/promote/result)
- real_chain_e2e:真轉檔→PromoteToModels→model 進 PG→冪等 全鏈路(對 stage 跑 PASS)

交接檔(給對應團隊根治):
- mc-email-claim-handoff:MC 加 email claim(自訂 UserClaimsPrincipalFactory)
- converter-promote-oauth-handoff:轉檔服務 OAuth 用 form body 非 Basic Auth

全程 Reviewer 審查 + 對 stage 真環境驗證。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 03:18:59 +08:00
dc1c0dbee4 feat(visionA-frontend): B4 model 詳細頁顯示 metadata(input_shape/classes/framework)
對應 backend B4:model 詳細頁顯示轉檔帶來的 metadata。
- Model 型別加 inputShape/classes/framework(optional);normalize 雙吃 snake/camel
- input_shape 顯示成 1 × 3 × 224 × 224;classes 前 8 個 + N 折疊;framework 直接顯示
- 有值才顯示(沒值不渲染空白);中英 i18n
- tsc / lint / next build 綠

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 06:37:36 +08:00
53e8ab4ae1 feat(visionA-frontend): Phase 0.9 模型庫下載 — 前端對接 FAA delegated download
對齊 ADR-017 v1.2 決策 2:model-card 下載按鈕 → 打 visionA endpoint 拿 url+token
→ fetch 跨 origin 直連 FAA + Authorization Bearer → blob 觸發下載(不經 visionA、不經 AWS)。

- src/lib/api/model-download.ts: getModelDownload(打 GET /api/models/:id/download)+
  downloadModelFile(fetch + Bearer header + blob,credentials:"omit" 不帶 visionA cookie)+
  triggerBlobDownload(延遲 revoke + finally 釋放)+ deriveDownloadFilename + ModelDownloadError
- model-store: downloadingId 互斥(同時只一個下載、finally 必清)+ downloadModel action +
  isModelDownloadable(source==converted && status==ready)
- model-card: 下載按鈕(converted+ready 才顯示、上傳類隱藏;preventDefault+stopPropagation
  防觸發外層 Link 導航;loading;toast 錯誤)
- i18n zh/en: models.action.download.* / models.download.*(各 error code 對應訊息)

關鍵差異:模型庫下載跨 origin + 需 Bearer header,不能用 <a href> navigation(無法帶 header),
必須 fetch+blob。與既有 conversion download(同 origin navigation)分流。

測試: 43 unit + 互動 test(mock fetch / 按鈕互動 / 顯示條件 / error 分流);tsc/lint/build 全綠。
Reviewer: 0 Critical / 0 Major / 3 Minor / 4 Suggestion,通過(ADR 決策 2 規格 7/7 符合)。

待 stage 實測: FAA 端須設 CORS(允許 visionA origin + preflight Allow-Headers: Authorization),
撞到會落 network_error。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 04:50:17 +08:00
78c1343e9a fix(visionA-frontend): conversion init multipart 對齊 converter 規格 — ref_images + model_id
Phase 0.8b stage e2e 連環 2 個跨層 schema mismatch(5/2 寫 frontend 時對 converter spec 沒驗、5/16 stage e2e 第一次跑通才暴露)。

Bug #5 ref_images field name PHP-style 不對齊
- 現象:converter multer LIMIT_UNEXPECTED_FILE、visionA 透傳 → 400 validation_failed
- 原因:frontend `form.append('ref_images[]', img)`、converter `uploader.fields([{name:'ref_images',...}])`(Express/multer 標準、無 [])
- 修法:frontend `form.append('ref_images', img)`(FormData 多筆同 key、multer 自動收成陣列)

Bug #6 model_id 用 taskName 當值、converter 要 integer
- 現象(修完 Bug #5 後暴露):converter validator → 400 validation_error 「model_id 必須為非負整數」
- 原因:frontend `args.taskName ?? args.file.name` → 字串 "input" / "yolov5s_test";
       converter validator (`createJob.js:153-164`) 規定 integer 1-65535(KTC tool 內部 model 編號)
- 修法:新增 `generateConverterModelId()` helper(Math.random 1-65535)、每次 init 自動生成;
       taskName 留給 visionA UX(promote 後 model store 的 name),與 converter model_id 解耦

驗證:
- pnpm test src/lib/api/conversion.test 22/22 pass
- pnpm build 通過
- stage redeploy 兩次(commit ce6a657 後跑出 Bug #5、fix Bug #5 deploy 後跑出 Bug #6、fix Bug #6 已 deploy)

剩餘 e2e 待 user browser 真實上傳 ONNX file 驗證。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 11:21:23 +08:00
9ebf46112b feat(visionA-frontend): AuthGuard — 未登入自動導 /login + 記住原本 path
需求:
  使用者未登入訪 protected route(如 /conversion、/devices)→ 卡在空 UI、無 redirect。
  使用者反映「希望未登入直接跳登入頁、登入完跳回原本要去的頁」。

實作:
  - 新增 src/components/auth-guard.tsx:
    * public routes allowlist:/login, /register
    * protected route + user=null + hydrate 完成 → router.replace('/login?next=<path>')
    * hydrate 進行中 → render null(避免閃過 protected UI)
    * buildNextParam helper:排除 public routes 和純 / 路徑

  - 改 src/app/layout.tsx:
    * 用 <Suspense fallback={null}> 包 AuthGuard(next/navigation useSearchParams 規範)
    * AuthGuard 包 AppShell

  - 改 src/app/login/page.tsx:
    * buildLoginUrl 接受 returnTo param、組進 backend `/api/auth/login?return_to=<path>`
    * sanitizeNext helper:對齊 backend oidc_auth.go:382 sanitizeReturnTo("/" 開頭、無 "//"、無 "://")
    * 已登入 redirect:從 query ?next= 跳該 path(不再固定 /)

  - 改 src/app/login/login.test.tsx:
    * mock 補 useSearchParams(next/navigation mock 既有只 mock useRouter)

  - backend oidc_auth.go:88 sanitizeReturnTo 已支援 return_to query param、無需改 backend code

驗證:
  - tsc --noEmit 0 errors
  - pnpm lint 0 errors
  - pnpm build 通過(13 pages prerendered)
  - login.test.tsx 12/12 pass
  - stage deploy verify:SSR HTML 含 AuthGuard component bundle

不在本 commit 範圍:
  - .env.stage CORS fix(VISIONA_CORS_ALLOWED_ORIGINS、stage host .env 直接改、不進 git)
  - 5/16 deploy script typo fix(已在 commit fad17dd)
  - converter multipart field mismatch(converter scheduler 跨 repo 處理)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 10:38:16 +08:00
700b7b08ba chore(stage): 新增 v2 deploy 流程(remote build via DOCKER_HOST)
v1 (deploy-stage.sh) 走 docker save | gzip | docker load 模式,需要把 81MB
tarball 一次性 POST 到 stage docker daemon /images/load API。5/4 / 5/9 兩次
驗證 VPN 下 docker daemon 對單一大 POST hang(卡 30+ 分鐘 / i/o timeout),
公司網段直連才可靠。

v2 仿 edge-ai-platform/scripts/deploy-docker.sh 改用 DOCKER_HOST=stage
docker build — multi-stage build 完全在 stage daemon 上執行:
- 跨網路只傳 build context(~44 MB streaming,VPN 友善)
- alpine base / nodejs / go mod / pnpm install 都由 stage daemon 自己 pull
- layer cache 留在 stage daemon,後續 incremental build 更快
- 5/9 VPN 下實測 work:first build ~3min、redeploy(layer cache) ~10s

連帶修:
- pnpm-workspace.yaml: 加 onlyBuiltDependencies (sharp / unrs-resolver /
  @tailwindcss/oxide / esbuild) — pnpm 10 預設拒跑依賴 build script、
  乾淨環境第一次 install 撞 ERR_PNPM_IGNORED_BUILDS
- package.json: 加 packageManager: pnpm@10.30.1 — 鎖 pnpm 版本,corepack
  在 stage daemon 第一次跑時不會拉到最新 pnpm 11(行為差異)
- Dockerfile.stage: COPY pnpm-workspace.yaml 進 builder context、否則
  容器內 install 看不到 trust list

v1 (deploy-stage.sh) 保留作為公司網段直連備援;v2 是 VPN / 預設模式。
2026-05-11 10:35:21 +08:00
e02059eff2 feat(visionA-frontend): Phase 0.8 conversion UI — sidebar tab + 6 view + 5 e2e flow tests
Phase 0.8 對應後端轉檔功能的 frontend UI。完整 state machine(idle / uploading /
queued / running / succeeded / failed / expired)+ XHR upload progress + polling +
half-auto result handling(加到模型庫 / 下載)。

新增 src/app/conversion/(11 元件 + 12 test files,~5,000 行 prod+test,310/310 tests 全綠):

- page.tsx:state router(mount → bootstrap → 依 store.uiState 切換 view)+ toast on
  store.error(aborted / active_job_exists 不 toast,避免雙重提示)
- IdleForm.tsx + FileDropzone.tsx + ChipSelector.tsx:上傳表單(拖放 + .onnx/.tflite
  format 驗證 + 500MB 大小限制 + KL520/630/720/730 chip 選擇 + ref images ≤100×10MB)
- UploadingView.tsx:XHR upload progress 0-100% + EWMA ETA(alpha=0.3 平滑,<5s 顯示
  「即將完成」避抖動)+ beforeunload warning + AlertDialog 取消
- ProcessingView.tsx:StageIndicator 三段式(解析模型 → 量化編譯 → 編譯 NEF)+
  Progress bar 三模式(queued / determinate / indeterminate)+ tab title `(轉檔中)` 防疊加 +
  409 active_job_exists banner(switchedFromActiveJob flag)
- SuccessView.tsx + PromoteDialog.tsx:兩按鈕(加到模型庫 / 下載)視覺平衡不暗示預設答案 +
  7 天 expires_at 倒數(mount 時 setTimeout 鎖過期那刻 + 60s tick)+ PromoteDialog 單欄位
  name 驗證(≤100 字元 / 無 /\\)+ spinner-during-close 阻擋 + 409 already_imported 特殊
  訊息 + toast.success router.push 跳模型清單
- FailedView.tsx:5 個 known error code 翻譯(UNSUPPORTED_FORMAT / INVALID_CHECKSUM /
  QUANTIZATION_FAILED / MODEL_TOO_LARGE / QUOTA_EXCEEDED)+ unknown fallback +
  3 個建議解決方法 + Job ID 前 8 字元(供回報)+ 「重新開始」
- ExpiredView.tsx:橘色 hero(過期不 alarming)+ source/chip 摘要 + 「重新轉檔」→
  store.reset()

新增 src/stores/conversion-store.ts(Zustand store + 29 tests):
- 7 個 UI state machine(不允許跳階段)
- recursive setTimeout polling(running 5s / queued 10s / 5xx 指數退避 cap 30s)+
  visibilitychange 暫停/恢復
- 不持久化 jobId(純靠 backend getActiveConversion() lazy rebuild ownership)
- AbortController 防 stale request + 取消上傳
- switchedFromActiveJob flag(409 自動切到既存 job + UI 顯示 banner)
- formDraft(chip / taskName 提到 store,failed→idle 後保留設定,file picker 重選;
  File 物件不能序列化只留 local)

新增 src/lib/api/conversion.ts + types/conversion.ts(5 client 函式 + 22 tests):
- initConversion:XHR multipart + onUploadProgress + AbortSignal
- getActiveConversion / getConversion / promoteConversionToModels:標準 fetch
- getConversionDownloadURL:純函式,回 `/api/conversion/{job_id}/download` 給 anchor
  download 觸發(server-side 302 → FAA,token 不過 frontend JS)
- ConversionAPIError(status, code, message, requestId?),code 統一全小寫對齊
  conversion.error.<code> i18n key 命名

新增 src/lib/utils/eta.ts(EWMA 演算法純函式 + 19 tests):抽出 smoothSpeed /
estimateRemainingSeconds / instantSpeedBytesPerSec / computeEtaUpdate(test 友善)。

新增 src/app/conversion/e2e-conversion-flow.test.tsx(5 e2e flow tests):
- happy path .onnx + KL720 + 0 ref images(idle → upload → polling → succeeded → 加到模型庫)
- variant 5 ref images
- upload fail → retry(form 設定保留)
- polling 5xx 指數退避 → 恢復繼續
- expired job → ExpiredView → 重新轉檔

修改 sidebar.tsx:左側 nav 新增「轉檔」tab(Wand2 icon,模型庫之後)。
修改 i18n 字典:新增 ~150 個 conversion.* keys(中英雙語對齊)。

PRD §9 14 條功能驗收條件全達成(4 條整合驗收等 stage 部署)。

驗證:pnpm lint / test (310/310) / build 全綠。

對齊文件:
- .autoflow/02-prd/features/feature-converter-integration.md
- .autoflow/03-design/wireframes/wireframe-conversion.md
- .autoflow/03-design/flows/flow-conversion.md
- .autoflow/04-architecture/api/api-conversion.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 13:56:54 +08:00
99dea42239 feat(visionA-frontend): Phase 0 → 0.7 雲端前端(Next.js + OIDC redirect 流程)
visionA 雲端版前端 — 沿用 local-tool 既有 UI(原則 4:先抄 local-tool)+
新增雲端特有的登入 / 配對 / 設定流程,含以下整合階段:

- Phase 0:13 頁 + 30+ 元件 + 雛形 banner
  - dashboard / devices / models / workspace / clusters / settings 等頁
  - AppShell + Sidebar + Header + tokens + i18n(中英雙語 96 keys)
  - API client + 5 stores + 3 hooks
- Phase 0.6:OIDC redirect 改造
  - login 頁改為 OIDC redirect(`window.location.href = /api/auth/login`)
  - register 改說明頁、account 改唯讀(user 資料來源是 MC)
  - api client 改 cookie session(credentials: include)+ 完全清掉 localStorage
- Phase 0.7:stage 部署 + nil guard
  - getApiBaseUrl() 修:browser 環境視為 same-origin(與 login 頁一致)
  - login 頁加「已登入 → router.replace('/')」effect
  - User type email/name 改 optional(MC id_token 不一定回 email/name claim)
  - header.tsx UserMenu displayName 4 層 fallback:name → email → id → i18n
  - 雛形 banner 文案更新(已接 Innovedus 帳號中心)+ 版號 Phase 0.7

驗證:pnpm lint / test (125/125) / build 全綠

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 11:21:36 +08:00