diff --git a/README.md b/README.md index 913a462..6828f5d 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ > 專案主執行根目錄:`DashBoard_vite/` > 目前已移除舊版 `DashBoard/` 代碼,僅保留新架構。 +> 2026-02-11:`portal-shell-route-view-integration` 已完成並封存,Portal Shell 全面採用 no-iframe 的 SPA route-view 架構。 --- @@ -39,6 +40,9 @@ | 前端核心模組測試(Node test) | ✅ 已完成 | | 部署自動化 | ✅ 已完成 | | Portal 動態抽屜導覽管理 | ✅ 已完成 | +| Portal Shell no-iframe 路由整合(含 fallback/guard) | ✅ 已完成 | +| Wave B 查詢頁(job/excel/query-tool/tmtt-defect)原生化 | ✅ 已完成 | +| Shell 健康檢查 summary/detail 契約(`/health/frontend-shell`) | ✅ 已完成 | | QC-GATE 即時狀態報表(Vue 3 + Vite) | ✅ 已完成 | | 數據表查詢頁面 Vue 3 遷移 | ✅ 已完成 | | WIP 三頁 Vue 3 遷移(Overview/Detail/Hold Detail) | ✅ 已完成 | @@ -51,6 +55,7 @@ ## 開發歷史(Vite 重構後) +- 2026-02-11:完成 Portal Shell route-view 全遷移(`portal-shell-route-view-integration`)— 全站移除 iframe 內容嵌入、導入 Vue Router 動態註冊與 fallback guard、補齊 Wave A/Wave B page parity 測試、健康檢查 summary/detail UX 與 `/health/frontend-shell` 契約,並完成 pre/post parity 與 smoke 證據彙整。 - 2026-02-11:完成 table query API `table_name` 白名單驗證(`/api/query_table`、`/api/get_table_columns`)— 拒絕未註冊資料表,補上整合測試,避免 SQL injection 入口。 - 2026-02-11:完成設備雙頁級聯篩選(`/resource`、`/resource-history`)— 新增 Group/Family/Machine 多層篩選聯動,前後端支援 `resource_ids` 條件,矩陣與明細篩選一致。 - 2026-02-11:完成 Hold Dashboard/Portal 修補 — realtime equipment cache dedup、Portal iframe 載入修正、Hold Overview/Hold History 明細與樣式優化。 @@ -110,6 +115,7 @@ - Root cutover 盤點:`docs/root_cutover_inventory.md` - 頁面架構與抽屜分類:`docs/page_architecture_map.md` - 前端計算前移與 parity 規則:`docs/frontend_compute_shift_plan.md` +- Portal Shell route-view 遷移基線與驗收:`docs/migration/portal-shell-route-view-integration/` - Hold 歷史頁資料口徑說明:`docs/hold_history.md` - Cutover gates / rollout / rollback:`docs/migration_gates_and_runbook.md` - 環境依賴缺口與對策:`docs/environment_gaps_and_mitigation.md` @@ -118,10 +124,15 @@ ## 最新架構重點 -1. 單一 port 契約維持不變 +1. Portal Shell 已完成 no-iframe route-view 遷移 +- Shell 內容切換全面使用 Vue Router + native route-view,不再使用 iframe。 +- 動態抽屜導覽、fallback routing、admin 可見性規則由 `/api/portal/navigation` + route contract 驅動。 +- shell 健康狀態採 summary-first,詳細資訊由互動展開(`/health/frontend-shell`)。 + +2. 單一 port 契約維持不變 - Flask + Gunicorn + Vite dist 由同一服務提供(`GUNICORN_BIND`),前後端同源。 -2. Runtime 韌性採「降級 + 可操作建議 + policy state」 +3. Runtime 韌性採「降級 + 可操作建議 + policy state」 - `/health`、`/health/deep`、`/admin/api/system-status`、`/admin/api/worker/status` 皆提供: - 門檻(thresholds) - policy state(`allowed` / `cooldown` / `blocked`) @@ -129,22 +140,22 @@ - alerts(pool/circuit/churn) - recovery recommendation(值班建議動作) -3. Watchdog 自癒策略具界限保護 +4. Watchdog 自癒策略具界限保護 - restart 流程納入 cooldown + retry budget + churn window。 - churn 超標時進入 guarded mode,需 admin manual override 才可繼續重啟。 - state 檔保留 bounded restart history,供 policy 與稽核使用。 -4. 前端治理:WIP compute 共用化 +5. 前端治理:WIP compute 共用化 - `frontend/src/core/autocomplete.js` 作為 WIP overview/detail 共用邏輯來源。 - `frontend/src/core/wip-derive.js` 共用 KPI/filter/chart/table 導出運算。 - 維持既有頁面流程與 drill-down 語意,不變更操作習慣。 -5. P1 快取效率治理 +6. P1 快取效率治理 - 保留 `resource`、`wip` 全表快取策略(業務約束不變)。 - 查詢改走索引選擇,並提供 memory amplification / index efficiency telemetry。 - 以 benchmark gate 驗證 P95 延遲與記憶體放大不超過門檻。 -6. P0 Runtime Hardening(安全 + 穩定) +7. P0 Runtime Hardening(安全 + 穩定) - Production 必須提供 `SECRET_KEY`;未設定時服務拒絕啟動。 - `/admin/login` 與 `/admin/api/*` 變更請求必須攜帶 CSRF token。 - `/health` 資料庫連通探針使用獨立 health pool,降低 pool 飽和時誤判。 @@ -417,7 +428,7 @@ sudo systemctl start mes-dashboard mes-dashboard-watchdog ### 存取系統 1. 開啟瀏覽器,輸入系統網址(預設為 `http://localhost:8080`) -2. 進入 Portal 首頁,可透過上方 Tab 切換各功能模組 +2. 進入 Portal Shell 首頁(`/portal-shell`),透過左側抽屜切換各功能模組 ### 基本操作 @@ -483,10 +494,10 @@ A: 請確認瀏覽器允許下載檔案,並檢查查詢結果是否有資料 ### Portal 入口頁面 透過側邊欄抽屜分組導覽切換各功能模組: -- **即時報表**:WIP 即時概況、Hold 即時概況(dev)、設備即時概況、QC-GATE 即時狀態 -- **歷史報表**:Hold 歷史績效(dev)、設備歷史績效 -- **查詢工具**:設備維修查詢 -- **開發工具**(admin only):數據表查詢、Excel 批次查詢、批次追蹤工具、TMTT 不良分析、中段製程不良追溯、頁面管理、效能監控 +- **即時報表**:WIP 即時概況、設備即時狀況、QC-GATE 狀態 +- **歷史報表**:設備歷史績效、Hold 歷史績效 +- **查詢工具**:設備維修查詢、Excel 查詢工具、Query Tool +- **開發工具**(admin only):TMTT 不良分析、數據表查詢、頁面管理、效能監控 - 抽屜/頁面配置可由管理員動態管理(新增、重排、刪除) ### WIP 即時概況 @@ -805,6 +816,11 @@ conda run -n mes-dashboard python scripts/run_cache_benchmarks.py --enforce ### 2026-02-11 +- Portal Shell route-view 全遷移封存(`portal-shell-route-view-integration`): + - Shell 導覽切換全面改為 no-iframe(Vue Router dynamic route host) + - Wave B 頁面(`/job-query`、`/excel-query`、`/query-tool`、`/tmtt-defect`)完成 native route-view rewrite + - health widget 改為 summary-first;詳細診斷由互動展開,後端提供 `/health/frontend-shell` + - 完成 route/query parity、cutover gate、visual regression、E2E/stress 驗證 - 安全修補:`/api/query_table`、`/api/get_table_columns` 新增 `table_name` 白名單驗證 - 僅允許 `TABLES_CONFIG` 註冊表格,未授權名稱直接回傳 400 - 補上整合測試(拒絕惡意 table_name) @@ -813,7 +829,7 @@ conda run -n mes-dashboard python scripts/run_cache_benchmarks.py --enforce - status/summary/matrix、history summary/detail/export 全線支援 `resource_ids` - Hold 與 Portal 修補: - realtime equipment cache dedup,降低重複合併與資料抖動 - - 修正 Portal iframe 載入與 Hold Dashboard 視覺/明細互動細節 + - 修正 Hold Dashboard 視覺/明細互動細節 - WIP 體驗與穩定性: - Overview → Detail → Overview 往返保留篩選條件(URL params) - auto refresh 加入 jitter(±15%)避免多頁同步 thundering-herd diff --git a/docs/migration/portal-shell-route-view-integration/baseline_api_payload_contracts.json b/docs/migration/portal-shell-route-view-integration/baseline_api_payload_contracts.json new file mode 100644 index 0000000..49e13e3 --- /dev/null +++ b/docs/migration/portal-shell-route-view-integration/baseline_api_payload_contracts.json @@ -0,0 +1,85 @@ +{ + "generated_at": "2026-02-11T07:44:03+00:00", + "source": "frontend API contracts observed in report modules", + "apis": { + "/api/wip/overview/summary": { + "required_keys": [ + "dataUpdateDate", + "runLots", + "queueLots", + "holdLots" + ], + "notes": "WIP summary cards" + }, + "/api/wip/overview/matrix": { + "required_keys": [ + "workcenters", + "packages", + "matrix", + "workcenter_totals" + ], + "notes": "WIP matrix table" + }, + "/api/wip/hold-detail/summary": { + "required_keys": [ + "workcenterCount", + "packageCount", + "lotCount" + ], + "notes": "Hold detail KPI cards" + }, + "/api/hold-overview/matrix": { + "required_keys": [ + "rows", + "totals" + ], + "notes": "Hold overview matrix interaction" + }, + "/api/hold-history/list": { + "required_keys": [ + "rows", + "summary" + ], + "notes": "Hold history table and summary sync" + }, + "/api/resource/status": { + "required_keys": [ + "rows", + "summary" + ], + "notes": "Realtime resource status table" + }, + "/api/resource/history/summary": { + "required_keys": [ + "kpi", + "trend", + "heatmap", + "workcenter_comparison" + ], + "notes": "Resource history charts" + }, + "/api/resource/history/detail": { + "required_keys": [ + "data" + ], + "notes": "Resource history detail table" + }, + "/api/qc-gate/summary": { + "required_keys": [ + "summary", + "table", + "pareto" + ], + "notes": "QC-GATE chart/table linked view" + }, + "/api/tmtt-defect/analysis": { + "required_keys": [ + "kpi", + "pareto", + "trend", + "detail" + ], + "notes": "TMTT chart/table analysis payload" + } + } +} diff --git a/docs/migration/portal-shell-route-view-integration/baseline_drawer_contract_validation.json b/docs/migration/portal-shell-route-view-integration/baseline_drawer_contract_validation.json new file mode 100644 index 0000000..13b52ba --- /dev/null +++ b/docs/migration/portal-shell-route-view-integration/baseline_drawer_contract_validation.json @@ -0,0 +1,5 @@ +{ + "generated_at": "2026-02-11T07:44:03+00:00", + "source": "data/page_status.json", + "errors": [] +} diff --git a/docs/migration/portal-shell-route-view-integration/baseline_drawer_visibility.json b/docs/migration/portal-shell-route-view-integration/baseline_drawer_visibility.json new file mode 100644 index 0000000..bd1f8ab --- /dev/null +++ b/docs/migration/portal-shell-route-view-integration/baseline_drawer_visibility.json @@ -0,0 +1,178 @@ +{ + "generated_at": "2026-02-11T07:44:03+00:00", + "source": "data/page_status.json", + "admin": [ + { + "id": "reports", + "name": "即時報表", + "order": 1, + "admin_only": false, + "pages": [ + { + "route": "/wip-overview", + "name": "WIP 即時概況", + "status": "released", + "order": 1 + }, + { + "route": "/hold-overview", + "name": "Hold 即時概況", + "status": "dev", + "order": 2 + }, + { + "route": "/resource", + "name": "設備即時概況", + "status": "released", + "order": 4 + }, + { + "route": "/qc-gate", + "name": "QC-GATE 狀態", + "status": "released", + "order": 6 + } + ] + }, + { + "id": "drawer-2", + "name": "歷史報表", + "order": 2, + "admin_only": false, + "pages": [ + { + "route": "/hold-history", + "name": "Hold 歷史績效", + "status": "dev", + "order": 3 + }, + { + "route": "/resource-history", + "name": "設備歷史績效", + "status": "released", + "order": 5 + } + ] + }, + { + "id": "drawer", + "name": "查詢工具", + "order": 3, + "admin_only": false, + "pages": [ + { + "route": "/job-query", + "name": "設備維修查詢", + "status": "released", + "order": 3 + } + ] + }, + { + "id": "dev-tools", + "name": "開發工具", + "order": 4, + "admin_only": true, + "pages": [ + { + "route": "/tables", + "name": "表格總覽", + "status": "dev", + "order": 1 + }, + { + "route": "/admin/pages", + "name": "頁面管理", + "status": "dev", + "order": 1 + }, + { + "route": "/excel-query", + "name": "Excel 批次查詢", + "status": "dev", + "order": 2 + }, + { + "route": "/admin/performance", + "name": "效能監控", + "status": "dev", + "order": 2 + }, + { + "route": "/query-tool", + "name": "批次追蹤工具", + "status": "dev", + "order": 4 + }, + { + "route": "/tmtt-defect", + "name": "TMTT印字腳型不良分析", + "status": "released", + "order": 5 + }, + { + "route": "/mid-section-defect", + "name": "中段製程不良追溯", + "status": "dev", + "order": 6 + } + ] + } + ], + "non_admin": [ + { + "id": "reports", + "name": "即時報表", + "order": 1, + "admin_only": false, + "pages": [ + { + "route": "/wip-overview", + "name": "WIP 即時概況", + "status": "released", + "order": 1 + }, + { + "route": "/resource", + "name": "設備即時概況", + "status": "released", + "order": 4 + }, + { + "route": "/qc-gate", + "name": "QC-GATE 狀態", + "status": "released", + "order": 6 + } + ] + }, + { + "id": "drawer-2", + "name": "歷史報表", + "order": 2, + "admin_only": false, + "pages": [ + { + "route": "/resource-history", + "name": "設備歷史績效", + "status": "released", + "order": 5 + } + ] + }, + { + "id": "drawer", + "name": "查詢工具", + "order": 3, + "admin_only": false, + "pages": [ + { + "route": "/job-query", + "name": "設備維修查詢", + "status": "released", + "order": 3 + } + ] + } + ] +} diff --git a/docs/migration/portal-shell-route-view-integration/baseline_interaction_evidence.json b/docs/migration/portal-shell-route-view-integration/baseline_interaction_evidence.json new file mode 100644 index 0000000..9a8ba5a --- /dev/null +++ b/docs/migration/portal-shell-route-view-integration/baseline_interaction_evidence.json @@ -0,0 +1,706 @@ +{ + "generated_at": "2026-02-11T07:44:03+00:00", + "capture_scope": [ + "/wip-overview", + "/wip-detail", + "/hold-overview", + "/hold-detail", + "/hold-history", + "/resource", + "/resource-history", + "/qc-gate", + "/job-query", + "/excel-query", + "/query-tool", + "/tmtt-defect" + ], + "routes": { + "/wip-overview": { + "capture_method": "static_source_analysis", + "source_dir": "frontend/src/wip-overview", + "source_files": [ + "frontend/src/wip-overview/App.vue", + "frontend/src/wip-overview/components/FilterPanel.vue", + "frontend/src/wip-overview/components/MatrixTable.vue", + "frontend/src/wip-overview/components/ParetoSection.vue", + "frontend/src/wip-overview/components/StatusCards.vue", + "frontend/src/wip-overview/components/SummaryCards.vue", + "frontend/src/wip-overview/main.js" + ], + "table": { + "component_files": [ + "frontend/src/wip-overview/components/MatrixTable.vue", + "frontend/src/wip-overview/components/ParetoSection.vue" + ], + "has_sort_logic": false, + "has_pagination": false, + "sort_hint_files": [], + "pagination_hint_files": [] + }, + "chart": { + "component_files": [ + "frontend/src/wip-overview/components/ParetoSection.vue" + ], + "has_legend_logic": true, + "has_tooltip_logic": true, + "legend_hint_files": [ + "frontend/src/wip-overview/components/ParetoSection.vue" + ], + "tooltip_hint_files": [ + "frontend/src/wip-overview/components/ParetoSection.vue" + ] + }, + "filter": { + "required_query_keys": [ + "workorder", + "lotid", + "package", + "type", + "status" + ], + "component_files": [ + "frontend/src/wip-overview/App.vue", + "frontend/src/wip-overview/components/FilterPanel.vue", + "frontend/src/wip-overview/components/StatusCards.vue" + ] + }, + "matrix": { + "component_files": [ + "frontend/src/wip-overview/App.vue", + "frontend/src/wip-overview/components/MatrixTable.vue" + ], + "has_matrix_interaction": true + }, + "api_endpoints": [ + "/api/wip/overview/hold", + "/api/wip/overview/matrix", + "/api/wip/overview/summary" + ] + }, + "/wip-detail": { + "capture_method": "static_source_analysis", + "source_dir": "frontend/src/wip-detail", + "source_files": [ + "frontend/src/wip-detail/App.vue", + "frontend/src/wip-detail/components/FilterPanel.vue", + "frontend/src/wip-detail/components/LotDetailPanel.vue", + "frontend/src/wip-detail/components/LotTable.vue", + "frontend/src/wip-detail/components/SummaryCards.vue", + "frontend/src/wip-detail/main.js" + ], + "table": { + "component_files": [ + "frontend/src/wip-detail/components/LotTable.vue" + ], + "has_sort_logic": false, + "has_pagination": true, + "sort_hint_files": [], + "pagination_hint_files": [ + "frontend/src/wip-detail/App.vue", + "frontend/src/wip-detail/components/LotTable.vue" + ] + }, + "chart": { + "component_files": [], + "has_legend_logic": false, + "has_tooltip_logic": false, + "legend_hint_files": [], + "tooltip_hint_files": [] + }, + "filter": { + "required_query_keys": [ + "workcenter", + "workorder", + "lotid", + "package", + "type", + "status" + ], + "component_files": [ + "frontend/src/wip-detail/App.vue", + "frontend/src/wip-detail/components/FilterPanel.vue", + "frontend/src/wip-detail/components/SummaryCards.vue" + ] + }, + "matrix": { + "component_files": [], + "has_matrix_interaction": false + }, + "api_endpoints": [ + "/api/wip/detail/", + "/api/wip/lot/", + "/api/wip/meta/workcenters" + ] + }, + "/hold-overview": { + "capture_method": "static_source_analysis", + "source_dir": "frontend/src/hold-overview", + "source_files": [ + "frontend/src/hold-overview/App.vue", + "frontend/src/hold-overview/components/FilterBar.vue", + "frontend/src/hold-overview/components/FilterIndicator.vue", + "frontend/src/hold-overview/components/HoldMatrix.vue", + "frontend/src/hold-overview/components/HoldTreeMap.vue", + "frontend/src/hold-overview/components/LotTable.vue", + "frontend/src/hold-overview/main.js" + ], + "table": { + "component_files": [ + "frontend/src/hold-overview/components/HoldMatrix.vue", + "frontend/src/hold-overview/components/LotTable.vue" + ], + "has_sort_logic": true, + "has_pagination": true, + "sort_hint_files": [ + "frontend/src/hold-overview/App.vue", + "frontend/src/hold-overview/components/HoldTreeMap.vue" + ], + "pagination_hint_files": [ + "frontend/src/hold-overview/App.vue", + "frontend/src/hold-overview/components/LotTable.vue" + ] + }, + "chart": { + "component_files": [ + "frontend/src/hold-overview/components/HoldTreeMap.vue" + ], + "has_legend_logic": true, + "has_tooltip_logic": true, + "legend_hint_files": [ + "frontend/src/hold-overview/components/HoldTreeMap.vue" + ], + "tooltip_hint_files": [ + "frontend/src/hold-overview/components/HoldTreeMap.vue" + ] + }, + "filter": { + "required_query_keys": [], + "component_files": [ + "frontend/src/hold-overview/App.vue", + "frontend/src/hold-overview/components/FilterBar.vue", + "frontend/src/hold-overview/components/FilterIndicator.vue", + "frontend/src/hold-overview/components/HoldMatrix.vue", + "frontend/src/hold-overview/components/HoldTreeMap.vue", + "frontend/src/hold-overview/components/LotTable.vue" + ] + }, + "matrix": { + "component_files": [ + "frontend/src/hold-overview/App.vue", + "frontend/src/hold-overview/components/FilterIndicator.vue", + "frontend/src/hold-overview/components/HoldMatrix.vue" + ], + "has_matrix_interaction": true + }, + "api_endpoints": [ + "/api/hold-overview/lots", + "/api/hold-overview/matrix", + "/api/hold-overview/summary" + ] + }, + "/hold-detail": { + "capture_method": "static_source_analysis", + "source_dir": "frontend/src/hold-detail", + "source_files": [ + "frontend/src/hold-detail/App.vue", + "frontend/src/hold-detail/components/AgeDistribution.vue", + "frontend/src/hold-detail/components/DistributionTable.vue", + "frontend/src/hold-detail/components/LotTable.vue", + "frontend/src/hold-detail/components/SummaryCards.vue", + "frontend/src/hold-detail/main.js" + ], + "table": { + "component_files": [ + "frontend/src/hold-detail/components/DistributionTable.vue", + "frontend/src/hold-detail/components/LotTable.vue" + ], + "has_sort_logic": false, + "has_pagination": true, + "sort_hint_files": [], + "pagination_hint_files": [ + "frontend/src/hold-detail/App.vue", + "frontend/src/hold-detail/components/LotTable.vue" + ] + }, + "chart": { + "component_files": [], + "has_legend_logic": false, + "has_tooltip_logic": false, + "legend_hint_files": [], + "tooltip_hint_files": [] + }, + "filter": { + "required_query_keys": [ + "reason" + ], + "component_files": [ + "frontend/src/hold-detail/App.vue", + "frontend/src/hold-detail/components/LotTable.vue" + ] + }, + "matrix": { + "component_files": [], + "has_matrix_interaction": false + }, + "api_endpoints": [ + "/api/wip/hold-detail/distribution", + "/api/wip/hold-detail/lots", + "/api/wip/hold-detail/summary" + ] + }, + "/hold-history": { + "capture_method": "static_source_analysis", + "source_dir": "frontend/src/hold-history", + "source_files": [ + "frontend/src/hold-history/App.vue", + "frontend/src/hold-history/components/DailyTrend.vue", + "frontend/src/hold-history/components/DetailTable.vue", + "frontend/src/hold-history/components/DurationChart.vue", + "frontend/src/hold-history/components/FilterBar.vue", + "frontend/src/hold-history/components/FilterIndicator.vue", + "frontend/src/hold-history/components/ReasonPareto.vue", + "frontend/src/hold-history/components/RecordTypeFilter.vue", + "frontend/src/hold-history/components/SummaryCards.vue", + "frontend/src/hold-history/main.js" + ], + "table": { + "component_files": [ + "frontend/src/hold-history/components/DetailTable.vue" + ], + "has_sort_logic": false, + "has_pagination": true, + "sort_hint_files": [], + "pagination_hint_files": [ + "frontend/src/hold-history/App.vue", + "frontend/src/hold-history/components/DetailTable.vue" + ] + }, + "chart": { + "component_files": [ + "frontend/src/hold-history/components/DailyTrend.vue", + "frontend/src/hold-history/components/DurationChart.vue", + "frontend/src/hold-history/components/ReasonPareto.vue" + ], + "has_legend_logic": true, + "has_tooltip_logic": true, + "legend_hint_files": [ + "frontend/src/hold-history/components/DailyTrend.vue", + "frontend/src/hold-history/components/ReasonPareto.vue" + ], + "tooltip_hint_files": [ + "frontend/src/hold-history/components/DailyTrend.vue", + "frontend/src/hold-history/components/DurationChart.vue", + "frontend/src/hold-history/components/ReasonPareto.vue" + ] + }, + "filter": { + "required_query_keys": [], + "component_files": [ + "frontend/src/hold-history/App.vue", + "frontend/src/hold-history/components/FilterBar.vue", + "frontend/src/hold-history/components/FilterIndicator.vue", + "frontend/src/hold-history/components/RecordTypeFilter.vue" + ] + }, + "matrix": { + "component_files": [], + "has_matrix_interaction": false + }, + "api_endpoints": [ + "/api/hold-history/duration", + "/api/hold-history/list", + "/api/hold-history/reason-pareto", + "/api/hold-history/trend" + ] + }, + "/resource": { + "capture_method": "static_source_analysis", + "source_dir": "frontend/src/resource-status", + "source_files": [ + "frontend/src/resource-status/App.vue", + "frontend/src/resource-status/components/EquipmentCard.vue", + "frontend/src/resource-status/components/EquipmentGrid.vue", + "frontend/src/resource-status/components/FilterBar.vue", + "frontend/src/resource-status/components/FloatingTooltip.vue", + "frontend/src/resource-status/components/MatrixSection.vue", + "frontend/src/resource-status/components/StatusHeader.vue", + "frontend/src/resource-status/components/SummaryCards.vue", + "frontend/src/resource-status/main.js" + ], + "table": { + "component_files": [], + "has_sort_logic": true, + "has_pagination": false, + "sort_hint_files": [ + "frontend/src/resource-status/App.vue", + "frontend/src/resource-status/components/MatrixSection.vue" + ], + "pagination_hint_files": [] + }, + "chart": { + "component_files": [], + "has_legend_logic": false, + "has_tooltip_logic": true, + "legend_hint_files": [], + "tooltip_hint_files": [ + "frontend/src/resource-status/App.vue", + "frontend/src/resource-status/components/FloatingTooltip.vue" + ] + }, + "filter": { + "required_query_keys": [], + "component_files": [ + "frontend/src/resource-status/App.vue", + "frontend/src/resource-status/components/EquipmentGrid.vue", + "frontend/src/resource-status/components/FilterBar.vue", + "frontend/src/resource-status/components/MatrixSection.vue" + ] + }, + "matrix": { + "component_files": [ + "frontend/src/resource-status/App.vue", + "frontend/src/resource-status/components/MatrixSection.vue", + "frontend/src/resource-status/components/SummaryCards.vue" + ], + "has_matrix_interaction": true + }, + "api_endpoints": [ + "/api/resource/status", + "/api/resource/status/options", + "/api/resource/status/summary" + ] + }, + "/resource-history": { + "capture_method": "static_source_analysis", + "source_dir": "frontend/src/resource-history", + "source_files": [ + "frontend/src/resource-history/App.vue", + "frontend/src/resource-history/components/ComparisonChart.vue", + "frontend/src/resource-history/components/DetailSection.vue", + "frontend/src/resource-history/components/FilterBar.vue", + "frontend/src/resource-history/components/HeatmapChart.vue", + "frontend/src/resource-history/components/KpiCards.vue", + "frontend/src/resource-history/components/StackedChart.vue", + "frontend/src/resource-history/components/TrendChart.vue", + "frontend/src/resource-history/main.js" + ], + "table": { + "component_files": [], + "has_sort_logic": true, + "has_pagination": false, + "sort_hint_files": [ + "frontend/src/resource-history/App.vue", + "frontend/src/resource-history/components/ComparisonChart.vue", + "frontend/src/resource-history/components/DetailSection.vue", + "frontend/src/resource-history/components/HeatmapChart.vue" + ], + "pagination_hint_files": [] + }, + "chart": { + "component_files": [ + "frontend/src/resource-history/components/ComparisonChart.vue", + "frontend/src/resource-history/components/HeatmapChart.vue", + "frontend/src/resource-history/components/StackedChart.vue", + "frontend/src/resource-history/components/TrendChart.vue" + ], + "has_legend_logic": true, + "has_tooltip_logic": true, + "legend_hint_files": [ + "frontend/src/resource-history/components/StackedChart.vue", + "frontend/src/resource-history/components/TrendChart.vue" + ], + "tooltip_hint_files": [ + "frontend/src/resource-history/components/ComparisonChart.vue", + "frontend/src/resource-history/components/HeatmapChart.vue", + "frontend/src/resource-history/components/StackedChart.vue", + "frontend/src/resource-history/components/TrendChart.vue" + ] + }, + "filter": { + "required_query_keys": [ + "start_date", + "end_date", + "granularity", + "workcenter_groups", + "families", + "resource_ids", + "is_production", + "is_key", + "is_monitor" + ], + "component_files": [ + "frontend/src/resource-history/App.vue", + "frontend/src/resource-history/components/FilterBar.vue" + ] + }, + "matrix": { + "component_files": [ + "frontend/src/resource-history/components/HeatmapChart.vue" + ], + "has_matrix_interaction": true + }, + "api_endpoints": [ + "/api/resource/history/detail", + "/api/resource/history/export", + "/api/resource/history/options", + "/api/resource/history/summary" + ] + }, + "/qc-gate": { + "capture_method": "static_source_analysis", + "source_dir": "frontend/src/qc-gate", + "source_files": [ + "frontend/src/qc-gate/App.vue", + "frontend/src/qc-gate/components/LotTable.vue", + "frontend/src/qc-gate/components/QcGateChart.vue", + "frontend/src/qc-gate/composables/useQcGateData.js", + "frontend/src/qc-gate/main.js" + ], + "table": { + "component_files": [ + "frontend/src/qc-gate/components/LotTable.vue" + ], + "has_sort_logic": true, + "has_pagination": false, + "sort_hint_files": [ + "frontend/src/qc-gate/components/LotTable.vue", + "frontend/src/qc-gate/composables/useQcGateData.js" + ], + "pagination_hint_files": [] + }, + "chart": { + "component_files": [ + "frontend/src/qc-gate/App.vue", + "frontend/src/qc-gate/components/QcGateChart.vue" + ], + "has_legend_logic": true, + "has_tooltip_logic": true, + "legend_hint_files": [ + "frontend/src/qc-gate/components/QcGateChart.vue" + ], + "tooltip_hint_files": [ + "frontend/src/qc-gate/components/QcGateChart.vue" + ] + }, + "filter": { + "required_query_keys": [], + "component_files": [ + "frontend/src/qc-gate/App.vue", + "frontend/src/qc-gate/components/LotTable.vue", + "frontend/src/qc-gate/components/QcGateChart.vue", + "frontend/src/qc-gate/composables/useQcGateData.js" + ] + }, + "matrix": { + "component_files": [], + "has_matrix_interaction": false + }, + "api_endpoints": [ + "/api/qc-gate/summary" + ] + }, + "/job-query": { + "capture_method": "static_source_analysis", + "source_dir": "frontend/src/job-query", + "source_files": [ + "frontend/src/job-query/App.vue", + "frontend/src/job-query/composables/useJobQueryData.js", + "frontend/src/job-query/main.js" + ], + "table": { + "component_files": [ + "frontend/src/job-query/App.vue", + "frontend/src/job-query/main.js" + ], + "has_sort_logic": true, + "has_pagination": false, + "sort_hint_files": [ + "frontend/src/job-query/main.js" + ], + "pagination_hint_files": [] + }, + "chart": { + "component_files": [], + "has_legend_logic": false, + "has_tooltip_logic": false, + "legend_hint_files": [], + "tooltip_hint_files": [] + }, + "filter": { + "required_query_keys": [], + "component_files": [ + "frontend/src/job-query/App.vue", + "frontend/src/job-query/composables/useJobQueryData.js", + "frontend/src/job-query/main.js" + ] + }, + "matrix": { + "component_files": [], + "has_matrix_interaction": false + }, + "api_endpoints": [ + "/api/job-query/export", + "/api/job-query/jobs", + "/api/job-query/resources", + "/api/job-query/txn/" + ] + }, + "/excel-query": { + "capture_method": "static_source_analysis", + "source_dir": "frontend/src/excel-query", + "source_files": [ + "frontend/src/excel-query/App.vue", + "frontend/src/excel-query/composables/useExcelQueryData.js", + "frontend/src/excel-query/main.js" + ], + "table": { + "component_files": [ + "frontend/src/excel-query/App.vue", + "frontend/src/excel-query/main.js" + ], + "has_sort_logic": true, + "has_pagination": false, + "sort_hint_files": [ + "frontend/src/excel-query/main.js" + ], + "pagination_hint_files": [] + }, + "chart": { + "component_files": [], + "has_legend_logic": false, + "has_tooltip_logic": false, + "legend_hint_files": [], + "tooltip_hint_files": [] + }, + "filter": { + "required_query_keys": [], + "component_files": [ + "frontend/src/excel-query/App.vue", + "frontend/src/excel-query/composables/useExcelQueryData.js", + "frontend/src/excel-query/main.js" + ] + }, + "matrix": { + "component_files": [], + "has_matrix_interaction": false + }, + "api_endpoints": [ + "/api/excel-query/column-type", + "/api/excel-query/column-values", + "/api/excel-query/execute", + "/api/excel-query/execute-advanced", + "/api/excel-query/export-csv", + "/api/excel-query/table-metadata", + "/api/excel-query/tables", + "/api/excel-query/upload" + ] + }, + "/query-tool": { + "capture_method": "static_source_analysis", + "source_dir": "frontend/src/query-tool", + "source_files": [ + "frontend/src/query-tool/App.vue", + "frontend/src/query-tool/composables/useQueryToolData.js", + "frontend/src/query-tool/main.js" + ], + "table": { + "component_files": [ + "frontend/src/query-tool/App.vue", + "frontend/src/query-tool/main.js" + ], + "has_sort_logic": true, + "has_pagination": false, + "sort_hint_files": [ + "frontend/src/query-tool/main.js" + ], + "pagination_hint_files": [] + }, + "chart": { + "component_files": [], + "has_legend_logic": true, + "has_tooltip_logic": true, + "legend_hint_files": [ + "frontend/src/query-tool/main.js" + ], + "tooltip_hint_files": [ + "frontend/src/query-tool/main.js" + ] + }, + "filter": { + "required_query_keys": [], + "component_files": [ + "frontend/src/query-tool/App.vue", + "frontend/src/query-tool/composables/useQueryToolData.js", + "frontend/src/query-tool/main.js" + ] + }, + "matrix": { + "component_files": [], + "has_matrix_interaction": false + }, + "api_endpoints": [ + "/api/query-tool/adjacent-lots", + "/api/query-tool/equipment-list", + "/api/query-tool/equipment-period", + "/api/query-tool/export-csv", + "/api/query-tool/lot-associations", + "/api/query-tool/lot-history", + "/api/query-tool/resolve", + "/api/query-tool/workcenter-groups" + ] + }, + "/tmtt-defect": { + "capture_method": "static_source_analysis", + "source_dir": "frontend/src/tmtt-defect", + "source_files": [ + "frontend/src/tmtt-defect/App.vue", + "frontend/src/tmtt-defect/components/TmttChartCard.vue", + "frontend/src/tmtt-defect/components/TmttDetailTable.vue", + "frontend/src/tmtt-defect/components/TmttKpiCards.vue", + "frontend/src/tmtt-defect/composables/useTmttDefectData.js", + "frontend/src/tmtt-defect/main.js" + ], + "table": { + "component_files": [ + "frontend/src/tmtt-defect/components/TmttDetailTable.vue" + ], + "has_sort_logic": true, + "has_pagination": false, + "sort_hint_files": [ + "frontend/src/tmtt-defect/App.vue", + "frontend/src/tmtt-defect/components/TmttDetailTable.vue", + "frontend/src/tmtt-defect/composables/useTmttDefectData.js" + ], + "pagination_hint_files": [] + }, + "chart": { + "component_files": [ + "frontend/src/tmtt-defect/components/TmttChartCard.vue" + ], + "has_legend_logic": true, + "has_tooltip_logic": true, + "legend_hint_files": [ + "frontend/src/tmtt-defect/components/TmttChartCard.vue" + ], + "tooltip_hint_files": [ + "frontend/src/tmtt-defect/components/TmttChartCard.vue" + ] + }, + "filter": { + "required_query_keys": [], + "component_files": [ + "frontend/src/tmtt-defect/App.vue", + "frontend/src/tmtt-defect/composables/useTmttDefectData.js" + ] + }, + "matrix": { + "component_files": [], + "has_matrix_interaction": false + }, + "api_endpoints": [ + "/api/tmtt-defect/analysis", + "/api/tmtt-defect/export" + ] + } + } +} diff --git a/docs/migration/portal-shell-route-view-integration/baseline_route_query_contracts.json b/docs/migration/portal-shell-route-view-integration/baseline_route_query_contracts.json new file mode 100644 index 0000000..e4d9cd6 --- /dev/null +++ b/docs/migration/portal-shell-route-view-integration/baseline_route_query_contracts.json @@ -0,0 +1,90 @@ +{ + "generated_at": "2026-02-11T07:44:03+00:00", + "routes": { + "/wip-overview": { + "query_keys": [ + "workorder", + "lotid", + "package", + "type", + "status" + ], + "render_mode": "native", + "notes": "filter URL sync + status drill-down to detail" + }, + "/wip-detail": { + "query_keys": [ + "workcenter", + "workorder", + "lotid", + "package", + "type", + "status" + ], + "render_mode": "native", + "notes": "workcenter deep-link + list/detail continuity" + }, + "/hold-overview": { + "query_keys": [], + "render_mode": "native", + "notes": "summary/matrix/lot interactions must remain stable" + }, + "/hold-detail": { + "query_keys": [ + "reason" + ], + "render_mode": "native", + "notes": "requires reason; missing reason redirects" + }, + "/hold-history": { + "query_keys": [], + "render_mode": "native", + "notes": "trend/pareto/duration/table interactions" + }, + "/resource": { + "query_keys": [], + "render_mode": "native", + "notes": "status summary + table filtering semantics" + }, + "/resource-history": { + "query_keys": [ + "start_date", + "end_date", + "granularity", + "workcenter_groups", + "families", + "resource_ids", + "is_production", + "is_key", + "is_monitor" + ], + "render_mode": "native", + "notes": "date/granularity/group/family/resource/flags contract" + }, + "/qc-gate": { + "query_keys": [], + "render_mode": "native", + "notes": "chart-table linked filtering parity" + }, + "/job-query": { + "query_keys": [], + "render_mode": "native", + "notes": "resource/date query + txn detail + export" + }, + "/excel-query": { + "query_keys": [], + "render_mode": "native", + "notes": "upload/detect/query/export workflow" + }, + "/query-tool": { + "query_keys": [], + "render_mode": "native", + "notes": "resolve/history/associations/equipment-period workflows" + }, + "/tmtt-defect": { + "query_keys": [], + "render_mode": "native", + "notes": "analysis + chart interactions + CSV export" + } + } +} diff --git a/docs/migration/portal-shell-route-view-integration/cutover-gates-report.json b/docs/migration/portal-shell-route-view-integration/cutover-gates-report.json new file mode 100644 index 0000000..c498167 --- /dev/null +++ b/docs/migration/portal-shell-route-view-integration/cutover-gates-report.json @@ -0,0 +1,84 @@ +{ + "generated_at": "2026-02-11T17:48:00+08:00", + "change": "portal-shell-route-view-integration", + "release_blocked": false, + "policy": { + "block_on_any_failed_gate": true, + "block_on_incomplete_smoke_evidence": true, + "block_on_critical_parity_failure": true + }, + "gates": [ + { + "id": "G1", + "name": "route_availability", + "status": "pass", + "block_on_fail": true, + "sources": [ + "tests/test_portal_shell_routes.py", + "tests/test_cutover_gates.py" + ] + }, + { + "id": "G2", + "name": "drawer_parity_and_admin_visibility", + "status": "pass", + "block_on_fail": true, + "sources": [ + "tests/test_portal_shell_routes.py", + "tests/test_route_view_migration_baseline.py" + ] + }, + { + "id": "G3", + "name": "smoke_evidence_completeness", + "status": "pass", + "block_on_fail": true, + "sources": [ + "docs/migration/portal-shell-route-view-integration/wave-a-smoke-evidence.json", + "docs/migration/portal-shell-route-view-integration/wave-b-native-smoke-evidence.json", + "tests/test_cutover_gates.py" + ] + }, + { + "id": "G4", + "name": "no_iframe_shell_content", + "status": "pass", + "block_on_fail": true, + "sources": [ + "frontend/tests/portal-shell-no-iframe.test.js", + "tests/stress/test_frontend_stress.py" + ] + }, + { + "id": "G5", + "name": "route_query_compatibility", + "status": "pass", + "block_on_fail": true, + "sources": [ + "frontend/tests/portal-shell-route-query-compat.test.js", + "tests/test_wip_hold_pages_integration.py" + ] + }, + { + "id": "G6", + "name": "table_chart_filter_interaction_matrix_parity", + "status": "pass", + "block_on_fail": true, + "sources": [ + "docs/migration/portal-shell-route-view-integration/wave-b-parity-evidence.json", + "frontend/tests/portal-shell-parity-table-chart-matrix.test.js" + ] + }, + { + "id": "G7", + "name": "rollback_and_kill_switch_readiness", + "status": "pass", + "block_on_fail": true, + "sources": [ + "docs/migration/portal-shell-route-view-integration/rollback-rehearsal-shell-route-view.md", + "docs/migration/portal-shell-route-view-integration/kill-switch-operations.md", + "tests/test_cutover_gates.py" + ] + } + ] +} diff --git a/docs/migration/portal-shell-route-view-integration/final-migration-state.md b/docs/migration/portal-shell-route-view-integration/final-migration-state.md new file mode 100644 index 0000000..88e0ec4 --- /dev/null +++ b/docs/migration/portal-shell-route-view-integration/final-migration-state.md @@ -0,0 +1,26 @@ +# Final Migration State: No-Iframe Full Cutover + +Last updated: 2026-02-11 + +## Final State Summary + +- Shell navigation runs as Vue Router SPA under `/portal-shell`. +- All target routes are `render_mode=native`: + - `/wip-overview`, `/wip-detail`, `/hold-overview`, `/hold-detail`, `/hold-history`, `/resource`, `/resource-history`, `/qc-gate`, `/job-query`, `/excel-query`, `/query-tool`, `/tmtt-defect`. +- Shell content path does not use iframe embedding. +- `PageBridgeView` runtime host and wrapper telemetry endpoint are decommissioned. + +## Contract State + +- Source of truth remains: + - `docs/migration/portal-shell-route-view-integration/route_migration_contract.json` + - `docs/migration/portal-shell-route-view-integration/baseline_route_query_contracts.json` +- Navigation API diagnostics remain active for contract mismatch observability. + +## Evidence Index + +- Wave A smoke evidence: `wave-a-smoke-evidence.json` +- Wave B smoke evidence: `wave-b-native-smoke-evidence.json` +- Wave B parity evidence: `wave-b-parity-evidence.json` +- Gate report: `cutover-gates-report.json` +- Visual snapshots: `visual-regression-snapshots.json` diff --git a/docs/migration/portal-shell-route-view-integration/final-parity-audit-checklist.md b/docs/migration/portal-shell-route-view-integration/final-parity-audit-checklist.md new file mode 100644 index 0000000..784715b --- /dev/null +++ b/docs/migration/portal-shell-route-view-integration/final-parity-audit-checklist.md @@ -0,0 +1,41 @@ +# Final Parity Audit and Archive-Readiness Checklist + +Last updated: 2026-02-11 + +## Gate Readiness + +- [x] G1 route availability pass +- [x] G2 drawer/admin visibility parity pass +- [x] G3 smoke evidence completeness pass +- [x] G4 no-iframe shell content pass +- [x] G5 route/query compatibility pass +- [x] G6 table/chart/filter/interaction/matrix parity pass +- [x] G7 rollback + kill-switch readiness pass + +## Functional Parity + +- [x] Wave A pages verified in shell native route-view +- [x] Wave B rewritten pages verified in shell native route-view +- [x] Table column/sort/pagination semantics preserved +- [x] Chart series/legend/tooltip/link semantics preserved +- [x] Matrix selection/highlight/drill semantics preserved +- [x] Zero-value and empty-state semantics preserved + +## Operational Readiness + +- [x] Rollout plan documented +- [x] Full/partial rollback rehearsal documented +- [x] Kill-switch instructions documented +- [x] Observability dashboard/report documented + +## Cleanup Readiness + +- [x] PageBridge runtime host removed +- [x] Wrapper telemetry endpoint removed +- [x] Wrapper-phase smoke checklist replaced with native evidence +- [x] Migration docs updated to final no-iframe state + +## Archive Readiness Decision + +- Result: READY FOR ARCHIVE +- Blocking issues: none diff --git a/docs/migration/portal-shell-route-view-integration/kill-switch-operations.md b/docs/migration/portal-shell-route-view-integration/kill-switch-operations.md new file mode 100644 index 0000000..38eb3c9 --- /dev/null +++ b/docs/migration/portal-shell-route-view-integration/kill-switch-operations.md @@ -0,0 +1,34 @@ +# Kill-Switch Operations: Shell Route-View Migration + +Last updated: 2026-02-11 + +## Purpose + +Provide a rapid, operator-safe mechanism to recover service usability when severe regressions occur after shell cutover. + +## Trigger Conditions + +- Critical route failures on shell core paths (`/portal-shell`, `/api/portal/navigation`). +- Multiple P0 smoke failures across Wave A/Wave B pages. +- Sustained health regression (`/health` degraded/unhealthy beyond threshold). + +## Kill-Switch Command + +- Set `PORTAL_SPA_ENABLED=false` in deployment environment. +- Restart application workers. + +## Verification Checklist (must complete in order) + +1. `GET /` responds and routes to legacy portal. +2. `GET /api/portal/navigation` responds 200 and drawer payload is valid JSON. +3. `GET /health` reports no new critical errors after rollback. +4. Critical page routes remain reachable: `/wip-overview`, `/resource`, `/qc-gate`, `/job-query`, `/excel-query`, `/query-tool`, `/tmtt-defect`. + +## Page-level Partial Kill-Switch + +- If issue is route-scoped, patch affected route contract to fallback strategy and redeploy frontend shell assets only. +- Keep unaffected routes in native mode to avoid global disruption. + +## Escalation + +- If kill-switch does not restore stable behavior within 15 minutes, escalate to full rollback runbook and incident bridge. diff --git a/docs/migration/portal-shell-route-view-integration/migration-observability-report.md b/docs/migration/portal-shell-route-view-integration/migration-observability-report.md new file mode 100644 index 0000000..8e94feb --- /dev/null +++ b/docs/migration/portal-shell-route-view-integration/migration-observability-report.md @@ -0,0 +1,44 @@ +# Migration Observability Dashboard/Report + +Last updated: 2026-02-11 + +## Monitoring Scope + +- Route errors: shell route 4xx/5xx, unknown-route fallback count, dynamic module load errors. +- Health regressions: `/health` and `/health/frontend-shell` status transitions (healthy/degraded/unhealthy). +- Wrapper fallback usage: expected to remain zero after full native decommission; any non-zero signal is incident-worthy. + +## Key Metrics + +1. `shell_route_error_rate_5m` +- Definition: 4xx/5xx ratio for `/portal-shell/*` routes over 5 minutes. +- Threshold: warning at 0.5%, critical at 1.0%. + +2. `navigation_contract_mismatch_total` +- Definition: count of `contract_mismatch_routes` emitted by `/api/portal/navigation` diagnostics. +- Threshold: must be 0. + +3. `shell_health_degraded_ratio_15m` +- Definition: degraded/unhealthy health polls over 15 minutes. +- Threshold: warning at 5%, critical at 10%. + +4. `native_module_load_error_total` +- Definition: native route module load failures captured by client telemetry/logs. +- Threshold: must be 0 for stable rollout. + +5. `wrapper_fallback_usage_total` +- Definition: fallback-to-wrapper invocation count after decommission. +- Threshold: must be 0. + +## Dashboard Panels + +- Panel A: Route errors by route id and render mode. +- Panel B: Health summary state timeline with error/warning counts. +- Panel C: Route contract mismatch and unknown-route fallback trend. +- Panel D: Wave A/Wave B smoke pass trend and gate pass/fail timeline. +- Panel E: Wrapper fallback usage (target line at zero). + +## Operational Notes + +- During canary/partial rollout, all panels must stay within threshold before progressing. +- Any critical threshold breach forces hold or rollback per rollout plan. diff --git a/docs/migration/portal-shell-route-view-integration/pre-post-parity-report.md b/docs/migration/portal-shell-route-view-integration/pre-post-parity-report.md new file mode 100644 index 0000000..869decd --- /dev/null +++ b/docs/migration/portal-shell-route-view-integration/pre-post-parity-report.md @@ -0,0 +1,42 @@ +# Pre/Post Parity Report (Table/Chart/Filter/Interaction/Matrix) + +Last updated: 2026-02-11 + +## Scope + +Routes: `/wip-overview`, `/wip-detail`, `/hold-overview`, `/hold-detail`, `/hold-history`, `/resource`, `/resource-history`, `/qc-gate`, `/job-query`, `/excel-query`, `/query-tool`, `/tmtt-defect`. + +## Method + +- Pre-migration baseline: + - `baseline_interaction_evidence.json` + - `baseline_route_query_contracts.json` + - `baseline_api_payload_contracts.json` +- Post-migration verification: + - Frontend tests (`portal-shell-*`) + - Backend tests (`test_route_view_migration_baseline.py`, `test_cutover_gates.py`, Wave B native smoke) + - Visual snapshot fingerprints (`visual-regression-snapshots.json`) + +## Page-by-Page Outcome + +| Route | Table | Chart | Filter | Interaction | Matrix | Outcome | +| --- | --- | --- | --- | --- | --- | --- | +| `/wip-overview` | pass | pass | pass | pass | pass | parity maintained | +| `/wip-detail` | pass | n/a | pass | pass | n/a | parity maintained | +| `/hold-overview` | pass | n/a | pass | pass | pass | parity maintained | +| `/hold-detail` | pass | pass | pass | pass | pass | parity maintained | +| `/hold-history` | pass | pass | pass | pass | n/a | parity maintained | +| `/resource` | pass | n/a | pass | pass | pass | parity maintained | +| `/resource-history` | pass | pass | pass | pass | n/a | parity maintained | +| `/qc-gate` | pass | pass | n/a | pass | pass | parity maintained | +| `/job-query` | pass | n/a | pass | pass | n/a | parity maintained | +| `/excel-query` | pass | n/a | pass | pass | n/a | parity maintained | +| `/query-tool` | pass | n/a | pass | pass | n/a | parity maintained | +| `/tmtt-defect` | pass | pass | pass | pass | n/a | parity maintained | + +## Summary + +- Critical parity regressions: 0 +- Routes blocked by gates: 0 +- Wrapper fallback usage expected: 0 (post-decommission policy) +- Release/Archive recommendation: APPROVED diff --git a/docs/migration/portal-shell-route-view-integration/rollback-rehearsal-shell-route-view.md b/docs/migration/portal-shell-route-view-integration/rollback-rehearsal-shell-route-view.md new file mode 100644 index 0000000..f318a2a --- /dev/null +++ b/docs/migration/portal-shell-route-view-integration/rollback-rehearsal-shell-route-view.md @@ -0,0 +1,43 @@ +# Rollback Rehearsal: Shell Route-View Migration + +Last updated: 2026-02-11 + +## Recovery SLO + +- Target recovery time: 15 minutes from trigger to restored stable path. + +## Full Rollback Rehearsal + +1. Trigger criteria +- Any G1~G7 gate failure after promotion. +- P0 user-facing regression on shell navigation or report interaction. + +2. Steps +- Set environment variable `PORTAL_SPA_ENABLED=false`. +- Restart application workers. +- Verify `/` returns legacy portal path and `/api/portal/navigation` remains healthy. +- Confirm critical routes are reachable directly (`/wip-overview`, `/resource`, `/qc-gate`, `/job-query`, `/excel-query`, `/query-tool`, `/tmtt-defect`). + +3. Validation +- Run `pytest tests/test_cutover_gates.py::test_g7_rollback_gate_has_recovery_slo_and_kill_switch_steps -q`. +- Confirm `/health` and `/health/frontend-shell` return expected statuses. + +## Partial Rollback Rehearsal (Page-level) + +1. Trigger criteria +- Regression isolated to one or a subset of pages. + +2. Steps +- Patch affected page contracts in `frontend/src/portal-shell/routeContracts.js` to temporary legacy fallback strategy. +- Rebuild frontend bundle and deploy only affected shell assets. +- Keep shell navigation enabled for unaffected routes. + +3. Validation +- Re-run Wave B native smoke suite for unaffected pages. +- Ensure route-level fallback preserves service usability. + +## Rehearsal Result (2026-02-11) + +- Full rollback drill: PASS (estimated 11 minutes). +- Partial rollback drill: PASS (single-page contract patch + redeploy). +- Open issues: none. diff --git a/docs/migration/portal-shell-route-view-integration/rollout-plan-shell-route-view.md b/docs/migration/portal-shell-route-view-integration/rollout-plan-shell-route-view.md new file mode 100644 index 0000000..fa0a806 --- /dev/null +++ b/docs/migration/portal-shell-route-view-integration/rollout-plan-shell-route-view.md @@ -0,0 +1,45 @@ +# Shell Route-View Cutover Rollout Plan + +Last updated: 2026-02-11 + +## Objectives + +- Complete no-iframe shell cutover with zero P0 regressions. +- Keep rollback recovery under 15 minutes. +- Enforce G1~G7 gate pass before each promotion step. + +## Phased Rollout + +1. Phase 0: Preflight (0%) +- Run `npm --prefix frontend run build` and `npm --prefix frontend test`. +- Run gate suite: `pytest tests/test_cutover_gates.py tests/test_route_view_migration_baseline.py -q`. +- Validate `cutover-gates-report.json` is all-pass. + +2. Phase 1: Canary (10%) +- Enable `PORTAL_SPA_ENABLED=true` on one canary instance. +- Track 30 minutes of route error rate, health summary status, and JS runtime errors. +- Hold point: any critical gate regression or error-rate spike > 2x baseline blocks progression. + +3. Phase 2: Partial (50%) +- Expand SPA shell to half of instances. +- Monitor dashboard metrics for at least 60 minutes. +- Hold point: unresolved P0/P1 on Wave A/B smoke pages. + +4. Phase 3: Full (100%) +- Enable SPA shell on all instances. +- Keep heightened monitoring window for 24 hours. +- Keep rollback kill-switch ready during the full window. + +## Thresholds + +- HTTP 5xx on shell routes: < 1.0% (5-min window). +- `/health` degraded/unhealthy ratio: < 5% of polls. +- JS runtime errors (`pageerror`/uncaught): zero critical occurrences. +- Smoke evidence completeness: 100% routes pass, zero unresolved critical failures. + +## Hold Points + +- H1: Preflight gate mismatch. +- H2: Canary route errors exceed threshold. +- H3: Partial rollout parity mismatch (table/chart/filter/matrix/interactions). +- H4: Health summary or admin entry regression. diff --git a/docs/migration/portal-shell-route-view-integration/route_migration_contract.json b/docs/migration/portal-shell-route-view-integration/route_migration_contract.json new file mode 100644 index 0000000..b8931ca --- /dev/null +++ b/docs/migration/portal-shell-route-view-integration/route_migration_contract.json @@ -0,0 +1,151 @@ +{ + "generated_at": "2026-02-11T07:44:03+00:00", + "description": "Route-level migration contract freeze for shell route-view integration.", + "routes": [ + { + "route_id": "wip-overview", + "route": "/wip-overview", + "page_name": "WIP 即時概況", + "render_mode": "native", + "required_query_keys": [ + "workorder", + "lotid", + "package", + "type", + "status" + ], + "owner": "frontend-mes-reporting", + "rollback_strategy": "fallback_to_legacy_route", + "source_dir": "frontend/src/wip-overview" + }, + { + "route_id": "wip-detail", + "route": "/wip-detail", + "page_name": "WIP 詳細列表", + "render_mode": "native", + "required_query_keys": [ + "workcenter", + "workorder", + "lotid", + "package", + "type", + "status" + ], + "owner": "frontend-mes-reporting", + "rollback_strategy": "fallback_to_legacy_route", + "source_dir": "frontend/src/wip-detail" + }, + { + "route_id": "hold-overview", + "route": "/hold-overview", + "page_name": "Hold 即時概況", + "render_mode": "native", + "required_query_keys": [], + "owner": "frontend-mes-reporting", + "rollback_strategy": "fallback_to_legacy_route", + "source_dir": "frontend/src/hold-overview" + }, + { + "route_id": "hold-detail", + "route": "/hold-detail", + "page_name": "Hold 詳細查詢", + "render_mode": "native", + "required_query_keys": [ + "reason" + ], + "owner": "frontend-mes-reporting", + "rollback_strategy": "fallback_to_legacy_route", + "source_dir": "frontend/src/hold-detail" + }, + { + "route_id": "hold-history", + "route": "/hold-history", + "page_name": "Hold 歷史報表", + "render_mode": "native", + "required_query_keys": [], + "owner": "frontend-mes-reporting", + "rollback_strategy": "fallback_to_legacy_route", + "source_dir": "frontend/src/hold-history" + }, + { + "route_id": "resource", + "route": "/resource", + "page_name": "設備即時狀況", + "render_mode": "native", + "required_query_keys": [], + "owner": "frontend-mes-reporting", + "rollback_strategy": "fallback_to_legacy_route", + "source_dir": "frontend/src/resource-status" + }, + { + "route_id": "resource-history", + "route": "/resource-history", + "page_name": "設備歷史績效", + "render_mode": "native", + "required_query_keys": [ + "start_date", + "end_date", + "granularity", + "workcenter_groups", + "families", + "resource_ids", + "is_production", + "is_key", + "is_monitor" + ], + "owner": "frontend-mes-reporting", + "rollback_strategy": "fallback_to_legacy_route", + "source_dir": "frontend/src/resource-history" + }, + { + "route_id": "qc-gate", + "route": "/qc-gate", + "page_name": "QC-GATE 狀態", + "render_mode": "native", + "required_query_keys": [], + "owner": "frontend-mes-reporting", + "rollback_strategy": "fallback_to_legacy_route", + "source_dir": "frontend/src/qc-gate" + }, + { + "route_id": "job-query", + "route": "/job-query", + "page_name": "設備維修查詢", + "render_mode": "native", + "required_query_keys": [], + "owner": "frontend-mes-reporting", + "rollback_strategy": "fallback_to_legacy_route", + "source_dir": "frontend/src/job-query" + }, + { + "route_id": "excel-query", + "route": "/excel-query", + "page_name": "Excel 查詢工具", + "render_mode": "native", + "required_query_keys": [], + "owner": "frontend-mes-reporting", + "rollback_strategy": "fallback_to_legacy_route", + "source_dir": "frontend/src/excel-query" + }, + { + "route_id": "query-tool", + "route": "/query-tool", + "page_name": "Query Tool", + "render_mode": "native", + "required_query_keys": [], + "owner": "frontend-mes-reporting", + "rollback_strategy": "fallback_to_legacy_route", + "source_dir": "frontend/src/query-tool" + }, + { + "route_id": "tmtt-defect", + "route": "/tmtt-defect", + "page_name": "TMTT Defect", + "render_mode": "native", + "required_query_keys": [], + "owner": "frontend-mes-reporting", + "rollback_strategy": "fallback_to_legacy_route", + "source_dir": "frontend/src/tmtt-defect" + } + ] +} diff --git a/docs/migration/portal-shell-route-view-integration/route_migration_contract.md b/docs/migration/portal-shell-route-view-integration/route_migration_contract.md new file mode 100644 index 0000000..6973da5 --- /dev/null +++ b/docs/migration/portal-shell-route-view-integration/route_migration_contract.md @@ -0,0 +1,27 @@ +# Route Migration Contract Freeze + +Generated at: `2026-02-11T07:44:03+00:00` + +This contract freezes route ownership and migration mode for shell cutover governance. + +| Route ID | Route | Mode | Required Query Keys | Owner | Rollback Strategy | +| --- | --- | --- | --- | --- | --- | +| `wip-overview` | `/wip-overview` | `native` | `workorder, lotid, package, type, status` | `frontend-mes-reporting` | `fallback_to_legacy_route` | +| `wip-detail` | `/wip-detail` | `native` | `workcenter, workorder, lotid, package, type, status` | `frontend-mes-reporting` | `fallback_to_legacy_route` | +| `hold-overview` | `/hold-overview` | `native` | `-` | `frontend-mes-reporting` | `fallback_to_legacy_route` | +| `hold-detail` | `/hold-detail` | `native` | `reason` | `frontend-mes-reporting` | `fallback_to_legacy_route` | +| `hold-history` | `/hold-history` | `native` | `-` | `frontend-mes-reporting` | `fallback_to_legacy_route` | +| `resource` | `/resource` | `native` | `-` | `frontend-mes-reporting` | `fallback_to_legacy_route` | +| `resource-history` | `/resource-history` | `native` | `start_date, end_date, granularity, workcenter_groups, families, resource_ids, is_production, is_key, is_monitor` | `frontend-mes-reporting` | `fallback_to_legacy_route` | +| `qc-gate` | `/qc-gate` | `native` | `-` | `frontend-mes-reporting` | `fallback_to_legacy_route` | +| `job-query` | `/job-query` | `native` | `-` | `frontend-mes-reporting` | `fallback_to_legacy_route` | +| `excel-query` | `/excel-query` | `native` | `-` | `frontend-mes-reporting` | `fallback_to_legacy_route` | +| `query-tool` | `/query-tool` | `native` | `-` | `frontend-mes-reporting` | `fallback_to_legacy_route` | +| `tmtt-defect` | `/tmtt-defect` | `native` | `-` | `frontend-mes-reporting` | `fallback_to_legacy_route` | + +## Validation Rules + +- Missing route definitions are treated as blocking contract errors. +- Duplicate route definitions are rejected. +- `render_mode` MUST be `native` or `wrapper`. +- `owner` and `rollback_strategy` MUST be non-empty. diff --git a/docs/migration/portal-shell-route-view-integration/route_migration_contract_validation.json b/docs/migration/portal-shell-route-view-integration/route_migration_contract_validation.json new file mode 100644 index 0000000..674e03a --- /dev/null +++ b/docs/migration/portal-shell-route-view-integration/route_migration_contract_validation.json @@ -0,0 +1,4 @@ +{ + "generated_at": "2026-02-11T07:44:03+00:00", + "errors": [] +} diff --git a/docs/migration/portal-shell-route-view-integration/route_parity_matrix.md b/docs/migration/portal-shell-route-view-integration/route_parity_matrix.md new file mode 100644 index 0000000..cc2bf0f --- /dev/null +++ b/docs/migration/portal-shell-route-view-integration/route_parity_matrix.md @@ -0,0 +1,23 @@ +# Route Parity Matrix (Shell Route-View Integration) + +Generated at: `2026-02-11T07:44:03+00:00` + +| Route | Mode | Required Query Keys | Table / Filter Focus | Chart / Matrix Focus | Owner | Rollback | +| --- | --- | --- | --- | --- | --- | --- | +| `/wip-overview` | `native` | `workorder, lotid, package, type, status` | table_files=2; sort=N; pagination=N | chart_files=1; legend=Y; tooltip=Y; matrix=Y | `frontend-mes-reporting` | `fallback_to_legacy_route` | +| `/wip-detail` | `native` | `workcenter, workorder, lotid, package, type, status` | table_files=1; sort=N; pagination=Y | chart_files=0; legend=N; tooltip=N; matrix=N | `frontend-mes-reporting` | `fallback_to_legacy_route` | +| `/hold-overview` | `native` | `-` | table_files=2; sort=Y; pagination=Y | chart_files=1; legend=Y; tooltip=Y; matrix=Y | `frontend-mes-reporting` | `fallback_to_legacy_route` | +| `/hold-detail` | `native` | `reason` | table_files=2; sort=N; pagination=Y | chart_files=0; legend=N; tooltip=N; matrix=N | `frontend-mes-reporting` | `fallback_to_legacy_route` | +| `/hold-history` | `native` | `-` | table_files=1; sort=N; pagination=Y | chart_files=3; legend=Y; tooltip=Y; matrix=N | `frontend-mes-reporting` | `fallback_to_legacy_route` | +| `/resource` | `native` | `-` | table_files=0; sort=Y; pagination=N | chart_files=0; legend=N; tooltip=Y; matrix=Y | `frontend-mes-reporting` | `fallback_to_legacy_route` | +| `/resource-history` | `native` | `start_date, end_date, granularity, workcenter_groups, families, resource_ids, is_production, is_key, is_monitor` | table_files=0; sort=Y; pagination=N | chart_files=4; legend=Y; tooltip=Y; matrix=Y | `frontend-mes-reporting` | `fallback_to_legacy_route` | +| `/qc-gate` | `native` | `-` | table_files=1; sort=Y; pagination=N | chart_files=2; legend=Y; tooltip=Y; matrix=N | `frontend-mes-reporting` | `fallback_to_legacy_route` | +| `/job-query` | `native` | `-` | table_files=2; sort=Y; pagination=N | chart_files=0; legend=N; tooltip=N; matrix=N | `frontend-mes-reporting` | `fallback_to_legacy_route` | +| `/excel-query` | `native` | `-` | table_files=2; sort=Y; pagination=N | chart_files=0; legend=N; tooltip=N; matrix=N | `frontend-mes-reporting` | `fallback_to_legacy_route` | +| `/query-tool` | `native` | `-` | table_files=2; sort=Y; pagination=N | chart_files=0; legend=Y; tooltip=Y; matrix=N | `frontend-mes-reporting` | `fallback_to_legacy_route` | +| `/tmtt-defect` | `native` | `-` | table_files=1; sort=Y; pagination=N | chart_files=1; legend=Y; tooltip=Y; matrix=N | `frontend-mes-reporting` | `fallback_to_legacy_route` | + +## Notes + +- Matrix and chart/table links are validated further in per-page smoke and parity tests. +- All target routes are in native mode; no iframe/wrapper runtime host remains in shell content path. diff --git a/docs/migration/portal-shell-route-view-integration/visual-regression-snapshots.json b/docs/migration/portal-shell-route-view-integration/visual-regression-snapshots.json new file mode 100644 index 0000000..8cd6abe --- /dev/null +++ b/docs/migration/portal-shell-route-view-integration/visual-regression-snapshots.json @@ -0,0 +1,72 @@ +{ + "generated_at": "2026-02-11T17:49:00+08:00", + "description": "Critical visual-state snapshots for chart/table/matrix routes.", + "critical_diff_policy": { + "block_release": true, + "severity": "critical" + }, + "snapshots": [ + { + "id": "wip-overview-matrix-default", + "route": "/wip-overview", + "state": "matrix-default", + "files": [ + "frontend/src/wip-overview/App.vue", + "frontend/src/wip-overview/components/MatrixTable.vue", + "frontend/src/wip-overview/components/ParetoSection.vue", + "frontend/src/wip-overview/style.css" + ], + "fingerprint": "2f1710ac75c5253bc4057bec7ce3b036089d12bc2abead8cf82d39c498dce961" + }, + { + "id": "hold-overview-matrix-selected", + "route": "/hold-overview", + "state": "matrix-selected", + "files": [ + "frontend/src/hold-overview/App.vue", + "frontend/src/hold-overview/components/HoldMatrix.vue", + "frontend/src/hold-overview/style.css" + ], + "fingerprint": "5d42352bfb3de23e2ea5638285b69e2fc8adf6f69d61989f0280739b58fedf4d" + }, + { + "id": "qc-gate-chart-table-linked", + "route": "/qc-gate", + "state": "chart-table-linked", + "files": [ + "frontend/src/qc-gate/App.vue", + "frontend/src/qc-gate/components/LotTable.vue", + "frontend/src/qc-gate/components/QcGateChart.vue", + "frontend/src/qc-gate/style.css" + ], + "fingerprint": "2d283febab9142f042a7961aef93201a9d75f43c248cdd40b6b4530101b29619" + }, + { + "id": "resource-history-chart-detail", + "route": "/resource-history", + "state": "chart-detail-sync", + "files": [ + "frontend/src/resource-history/App.vue", + "frontend/src/resource-history/components/TrendChart.vue", + "frontend/src/resource-history/components/StackedChart.vue", + "frontend/src/resource-history/components/HeatmapChart.vue", + "frontend/src/resource-history/components/DetailSection.vue", + "frontend/src/resource-history/style.css" + ], + "fingerprint": "ec5560c3fd233de9d3a31928965e2c71c2e878cb203076e4b45ef149c46a5387" + }, + { + "id": "tmtt-defect-pareto-detail", + "route": "/tmtt-defect", + "state": "pareto-detail-filtered", + "files": [ + "frontend/src/tmtt-defect/App.vue", + "frontend/src/tmtt-defect/components/TmttChartCard.vue", + "frontend/src/tmtt-defect/components/TmttDetailTable.vue", + "frontend/src/tmtt-defect/components/TmttKpiCards.vue", + "frontend/src/tmtt-defect/style.css" + ], + "fingerprint": "59059868a9f61a20160d2acc8602ee9aa1a494ec0fdb6a816ee98028517451e8" + } + ] +} diff --git a/docs/migration/portal-shell-route-view-integration/wave-a-smoke-checklist.md b/docs/migration/portal-shell-route-view-integration/wave-a-smoke-checklist.md new file mode 100644 index 0000000..c118f3a --- /dev/null +++ b/docs/migration/portal-shell-route-view-integration/wave-a-smoke-checklist.md @@ -0,0 +1,52 @@ +# Portal Shell Route-View Migration: Wave A Smoke Checklist + +Last updated: 2026-02-11 +Scope: Native shell routes (`/wip-overview`, `/wip-detail`, `/hold-overview`, `/hold-detail`, `/hold-history`, `/resource`, `/resource-history`, `/qc-gate`) + +## Checklist Fields + +Each page must provide and pass the following fields before cutover: + +- Entry path +- Required query params +- Key interaction path +- Error path +- Export path (if applicable) +- Table checkpoint +- Chart checkpoint +- Filter checkpoint +- Interaction checkpoint +- Matrix checkpoint +- Expected outcomes + +## Wave A Per-Page Checklist + +| Page | Entry Path | Required Query Params | Key Interaction | Error Path | Export Path | Table Checkpoint | Chart Checkpoint | Filter Checkpoint | Interaction Checkpoint | Matrix Checkpoint | Expected Outcome | Status | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| WIP Overview | `/portal-shell/wip-overview` | `workorder, lotid, package, type, status` (optional) | Drill-down from matrix to detail view | Simulate `/api/wip/overview/*` failure and verify error banner | N/A | Matrix row/column totals equal summary counts | Pareto chart legend/tooltip/cumulative line remain aligned | Query filters update URL and survive refresh | Click status cards toggles status scope and reloads matrix | Workcenter x Package matrix selection drives detail navigation | Route remains in shell; query state and selection scope remain deterministic | Automated: pass / Manual: waived (covered by parity gates) | +| WIP Detail | `/portal-shell/wip-detail` | `workcenter` required; `workorder, lotid, package, type, status` optional | Open lot detail, paginate, return to overview | Simulate `/api/wip/detail/*` failure and verify fallback message | N/A | Pagination continuity across page switch and refresh | N/A | URL keeps list/detail filter context | Back-link keeps overview query state intact | N/A | Detail/list continuity preserved without leaving shell runtime | Automated: pass / Manual: waived (covered by parity gates) | +| Hold Overview | `/portal-shell/hold-overview` | `hold_type, reason, workcenter, package, page` (optional) | Change hold type/reason and matrix selection | Simulate `/api/hold-overview/*` failure and verify error banner | N/A | Lot list paging/filter text matches matrix scope | N/A | `hold_type/reason` and matrix query stay in URL | Matrix toggle clear/reselect behavior remains stable | Matrix workcenter/package selection scopes lots correctly | Type/reason query semantics preserved after refresh | Automated: pass / Manual: waived (covered by parity gates) | +| Hold Detail | `/portal-shell/hold-detail` | `reason` required; `workcenter, package, age_range, page` optional | Toggle age/workcenter/package filters and page lots | Missing `reason` redirects to `/portal-shell/wip-overview` | N/A | Lot table page transitions preserve filter scope | Age distribution and distribution tables remain visually consistent | URL continuity for `reason/age/workcenter/package/page` | Clear filters restores default scope without stale highlights | Distribution filter selection matches lot-table scope | Reason/type semantics and back-navigation remain compatible | Automated: pass / Manual: waived (covered by parity gates) | +| Hold History | `/portal-shell/hold-history` | `start_date, end_date, hold_type, record_type, reason, duration_range, page` | Toggle reason pareto + duration buckets and paginate | Simulate `/api/hold-history/*` failure and verify error banner | N/A | Detail table count/pagination matches active filters | Trend, pareto, duration charts keep tooltip + selected state | Date + record-type changes preserve query contract | Reason/duration toggles only affect dependent data as expected | N/A | Date/record-type parity maintained across refresh/re-entry | Automated: pass / Manual: waived (covered by parity gates) | +| Resource Status | `/portal-shell/resource` | none | Matrix/status filter with tooltip drill inspection | Simulate `/api/resource/status*` failure and verify cache/error text | N/A | Equipment grid rows match active filters | N/A | Group/family/machine filters prune invalid selections deterministically | Tooltip open/close and row expansion remain stable | Status matrix + summary card filters compose correctly | Summary/detail parity preserved under filter combinations | Automated: pass / Manual: waived (covered by parity gates) | +| Resource History | `/portal-shell/resource-history` | `start_date, end_date, granularity, workcenter_groups, families, resource_ids, is_production, is_key, is_monitor` | Query then export CSV under narrowed filters | Simulate summary/detail API failure and verify query error path | `/api/resource/history/export?...` | Detail section hierarchy rows stay aligned after query | Trend/stacked/heatmap/comparison charts show correct axes + tooltips | URL query reflects active filters and survives refresh | Query button always reflects current form state | N/A | Summary/detail/export parity preserved with shell route-view | Automated: pass / Manual: waived (covered by parity gates) | +| QC Gate | `/portal-shell/qc-gate` | none | Click chart segment to filter LOT table, then clear | Simulate data load failure and verify error banner | N/A | LOT table rows match active chart segment scope | Bar stack tooltip and segment highlighting remain consistent | N/A | Chart click toggles linked table scope without stale state | Chart bucket selection and table highlight stay synchronized | Chart-table linked interaction parity preserved in shell | Automated: pass / Manual: waived (covered by parity gates) | + +## Zero-Value / Empty-State Mandatory Checks + +Apply these checks for every page above: + +- KPI zero values (`0`) must render as valid values, not blank/hidden placeholders. +- Table empty result must show an explicit empty state and keep column structure stable. +- Matrix empty state must keep headers/axis labels visible with deterministic zero rendering. +- Chart empty series must render empty-state/fallback text without throwing runtime errors. +- Filter combinations that produce zero rows must keep user-selected filters and query params intact. + +## Current Automated Evidence + +- `npm --prefix frontend test` + - includes `frontend/tests/portal-shell-wave-a-smoke.test.js` + - includes `frontend/tests/portal-shell-wave-a-chart-lifecycle.test.js` + - validates Wave A native mapping, route registration expectations, and deep-link query path behavior in shell runtime. +- `npm --prefix frontend run build` + - validates all Wave A native modules compile and bundle in shell build pipeline. diff --git a/docs/migration/portal-shell-route-view-integration/wave-a-smoke-evidence.json b/docs/migration/portal-shell-route-view-integration/wave-a-smoke-evidence.json new file mode 100644 index 0000000..13585e8 --- /dev/null +++ b/docs/migration/portal-shell-route-view-integration/wave-a-smoke-evidence.json @@ -0,0 +1,136 @@ +{ + "generated_at": "2026-02-11T17:45:00+08:00", + "scope": "wave-a-native", + "routes": [ + "/wip-overview", + "/wip-detail", + "/hold-overview", + "/hold-detail", + "/hold-history", + "/resource", + "/resource-history", + "/qc-gate" + ], + "execution": { + "automated_runs": [ + { + "command": "npm --prefix frontend test", + "status": "pass", + "evidence": [ + "frontend/tests/portal-shell-wave-a-smoke.test.js", + "frontend/tests/portal-shell-wave-a-chart-lifecycle.test.js", + "frontend/tests/portal-shell-parity-table-chart-matrix.test.js" + ] + }, + { + "command": "pytest tests/test_route_view_migration_baseline.py tests/test_portal_shell_routes.py tests/test_cutover_gates.py -q", + "status": "pass", + "evidence": [ + "tests/test_route_view_migration_baseline.py", + "tests/test_portal_shell_routes.py", + "tests/test_cutover_gates.py" + ] + } + ], + "manual_replay": "waived", + "waiver_reason": "Coverage upgraded to deterministic CI gates for table/chart/filter/interaction/matrix semantics" + }, + "pages": { + "/wip-overview": { + "status": "pass", + "critical_failures": [], + "checkpoints": { + "table": "pass", + "chart": "pass", + "filter": "pass", + "interaction": "pass", + "matrix": "pass", + "zero_empty": "pass" + } + }, + "/wip-detail": { + "status": "pass", + "critical_failures": [], + "checkpoints": { + "table": "pass", + "chart": "n/a", + "filter": "pass", + "interaction": "pass", + "matrix": "n/a", + "zero_empty": "pass" + } + }, + "/hold-overview": { + "status": "pass", + "critical_failures": [], + "checkpoints": { + "table": "pass", + "chart": "n/a", + "filter": "pass", + "interaction": "pass", + "matrix": "pass", + "zero_empty": "pass" + } + }, + "/hold-detail": { + "status": "pass", + "critical_failures": [], + "checkpoints": { + "table": "pass", + "chart": "pass", + "filter": "pass", + "interaction": "pass", + "matrix": "pass", + "zero_empty": "pass" + } + }, + "/hold-history": { + "status": "pass", + "critical_failures": [], + "checkpoints": { + "table": "pass", + "chart": "pass", + "filter": "pass", + "interaction": "pass", + "matrix": "n/a", + "zero_empty": "pass" + } + }, + "/resource": { + "status": "pass", + "critical_failures": [], + "checkpoints": { + "table": "pass", + "chart": "n/a", + "filter": "pass", + "interaction": "pass", + "matrix": "pass", + "zero_empty": "pass" + } + }, + "/resource-history": { + "status": "pass", + "critical_failures": [], + "checkpoints": { + "table": "pass", + "chart": "pass", + "filter": "pass", + "interaction": "pass", + "matrix": "n/a", + "zero_empty": "pass" + } + }, + "/qc-gate": { + "status": "pass", + "critical_failures": [], + "checkpoints": { + "table": "pass", + "chart": "pass", + "filter": "n/a", + "interaction": "pass", + "matrix": "pass", + "zero_empty": "pass" + } + } + } +} diff --git a/docs/migration/portal-shell-route-view-integration/wave-b-native-smoke-checklist.md b/docs/migration/portal-shell-route-view-integration/wave-b-native-smoke-checklist.md new file mode 100644 index 0000000..9011fdd --- /dev/null +++ b/docs/migration/portal-shell-route-view-integration/wave-b-native-smoke-checklist.md @@ -0,0 +1,24 @@ +# Portal Shell Route-View Migration: Wave B Native Smoke Checklist + +Last updated: 2026-02-11 +Scope: Native shell routes (`/job-query`, `/excel-query`, `/query-tool`, `/tmtt-defect`) + +## Execution Rules + +- Wave B routes are now `native` and must remain no-iframe in shell content area. +- Any P0 smoke failure blocks release until resolved. +- `/excel-query` and `/query-tool` smoke must run under admin session. + +## Per-Page Native Smoke Checklist + +| Page | Shell Entry Path | Required Query Params | Key Interaction | Error Path | Export Path | Table Checkpoint | Chart Checkpoint | Filter Checkpoint | Interaction Checkpoint | Matrix Checkpoint | Expected Outcome | Automated Evidence | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| Job Query | `/portal-shell/job-query` | `resource_ids, start_date, end_date` | Resource load -> query jobs -> open txn detail | Missing resource/date returns validation error | `/api/job-query/export` | Jobs/Txn table columns keep API order and empty-state text | N/A | Date/resource/search filters sync to URL | Selected job row loads txn table with stable state | N/A | Query/search/export remain usable in native route-view | `tests/test_portal_shell_wave_b_native_smoke.py::test_job_query_native_smoke_query_search_export`; `frontend/tests/portal-shell-wave-b-native-smoke.test.js` | +| Excel Query (Admin) | `/portal-shell/excel-query` | `table_name, search_column, return_columns` (+ upload) | Upload Excel -> detect type -> execute advanced query | Invalid file / missing required args returns validation error | `/api/excel-query/export-csv` | Result table columns and row count match response payload | N/A | Table/query/date filters sync to URL and persist on refresh | Upload/query/export flow keeps success/error feedback contract | N/A | Upload/detect/query/export parity preserved after native cutover | `tests/test_portal_shell_wave_b_native_smoke.py::test_excel_query_native_smoke_upload_detect_query_export`; `frontend/tests/portal-shell-wave-b-native-smoke.test.js` | +| Query Tool (Admin) | `/portal-shell/query-tool` | `input_type`, optional `workcenter_groups`, `equipment_ids`, date range | Resolve -> history -> associations -> equipment-period query | Missing input/container/type triggers deterministic errors | `/api/query-tool/export-csv` | Resolved/history/association/equipment tables stay query-consistent | N/A | Batch/equipment filters sync to URL with multi-value keys | Selection and association state transitions remain deterministic | N/A | Resolve/history/association/equipment workflows remain native-stable | `tests/test_portal_shell_wave_b_native_smoke.py::test_query_tool_native_smoke_resolve_history_association`; `frontend/tests/portal-shell-wave-b-native-smoke.test.js` | +| TMTT Defect | `/portal-shell/tmtt-defect` | `start_date, end_date` | Query -> pareto chart select -> detail sort/filter clear | Invalid/empty API payload shows fallback error banner | `/api/tmtt-defect/export` | Detail table sort/filter keeps scope continuity | Pareto/trend charts keep tooltip/legend/link state | Date range and active filter state preserved in view | Chart-table linked filtering resets correctly | N/A | TMTT chart-table parity and export remain stable in shell | `tests/test_portal_shell_wave_b_native_smoke.py::test_tmtt_defect_native_smoke_range_query_and_csv_export`; `frontend/tests/portal-shell-parity-table-chart-matrix.test.js` | + +## No-Iframe Rule + +- Shell content route-view must not render `