feat: finalize portal no-iframe migration baseline and archive change

This commit is contained in:
egg
2026-02-11 13:25:03 +08:00
parent cd54d7cdcb
commit ccab10bee8
117 changed files with 6673 additions and 1098 deletions

View File

@@ -0,0 +1,33 @@
# OpenSpec Archive Readiness (`portal-no-iframe-navigation`)
## Spec Sync Scope
Main specs synchronized/updated for this change:
- `openspec/specs/full-vite-page-modularization/spec.md`
- `openspec/specs/portal-drawer-navigation/spec.md`
- `openspec/specs/vue-vite-page-architecture/spec.md`
- `openspec/specs/migration-gates-and-rollout/spec.md`
- `openspec/specs/spa-shell-navigation/spec.md` (new)
- `openspec/specs/tailwind-design-system/spec.md` (new)
- `openspec/specs/frontend-motion-system/spec.md` (new)
- `openspec/specs/legacy-page-wrapper-strategy/spec.md` (new)
## Migration Closure Artifacts
- Rewrite smoke checklist:
- `docs/migration/portal-no-iframe/legacy_rewrite_smoke_checklists.md`
- Rewrite exemplar:
- `docs/migration/portal-no-iframe/tmtt_rewrite_exemplar.md`
- Rewrite playbook:
- `docs/migration/portal-no-iframe/legacy_rewrite_playbook.md`
- Wrapper decommission record:
- `docs/migration/portal-no-iframe/wrapper_decommission_report.md`
- Frame field retirement record:
- `docs/migration/portal-no-iframe/frame_id_tool_src_deprecation_plan.md`
## Pre-Archive Checklist
- [x] `openspec validate portal-no-iframe-navigation --strict` passes.
- [x] Build and core migration tests pass on latest branch.
- [x] Task list in `openspec/changes/portal-no-iframe-navigation/tasks.md` is fully checked.

View File

@@ -0,0 +1,46 @@
{
"source": "current frontend API consumption contracts",
"apis": {
"/api/wip/overview/summary": {
"required_keys": [
"dataUpdateDate",
"runLots",
"queueLots",
"holdLots"
],
"notes": "summary header and cards depend on these fields"
},
"/api/wip/overview/matrix": {
"required_keys": [
"workcenters",
"packages",
"matrix",
"workcenter_totals"
],
"notes": "matrix table rendering contract"
},
"/api/wip/hold-detail/summary": {
"required_keys": [
"workcenterCount",
"packageCount",
"lotCount"
],
"notes": "hold detail summary cards contract"
},
"/api/resource/history/summary": {
"required_keys": [
"kpi",
"trend",
"heatmap",
"workcenter_comparison"
],
"notes": "resource history chart summary contract"
},
"/api/resource/history/detail": {
"required_keys": [
"data"
],
"notes": "detail table contract (plus truncated/max_records metadata when present)"
}
}
}

View File

@@ -0,0 +1,4 @@
{
"source": "data/page_status.json",
"errors": []
}

View File

@@ -0,0 +1,177 @@
{
"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
}
]
}
]
}

View File

@@ -0,0 +1,46 @@
{
"source": "frontend route parsing and current parity matrix",
"routes": {
"/wip-overview": {
"query_keys": [
"workorder",
"lotid",
"package",
"type",
"status"
],
"notes": "filters + status URL state must remain compatible"
},
"/wip-detail": {
"query_keys": [
"workcenter",
"workorder",
"lotid",
"package",
"type",
"status"
],
"notes": "workcenter deep-link and back-link query continuity"
},
"/hold-detail": {
"query_keys": [
"reason"
],
"notes": "reason required for normal access flow"
},
"/resource-history": {
"query_keys": [
"start_date",
"end_date",
"granularity",
"workcenter_groups",
"families",
"resource_ids",
"is_production",
"is_key",
"is_monitor"
],
"notes": "query/export params must remain compatible"
}
}
}

View File

@@ -0,0 +1,50 @@
# Drawer Governance Contract (Portal No-Iframe Migration)
## Scope
This contract defines drawer behavior that must remain stable during migration.
## Canonical Responsibilities
Drawer metadata is responsible for:
- Information architecture grouping.
- Display order.
- Access visibility (`admin_only`).
Drawer metadata is not responsible for:
- Content embedding mode (`iframe`, `toolFrame`).
- Rendering technology selection (Jinja vs SPA route view).
## Contract Rules
1. Drawer IDs must be unique and non-empty.
2. Page routes must be unique and non-empty.
3. `page.drawer_id` (when present) must reference an existing drawer.
4. `order` values (when present) must be positive integers.
5. Page status must be one of `released` or `dev`.
6. Visibility outcomes must be deterministic for admin/non-admin users.
## Deterministic Rendering Order
Drawers:
- Primary sort by `order` ascending.
- Secondary sort by `name` ascending.
Pages in each drawer:
- Primary sort by `order` ascending.
- Secondary sort by `(name or route)` ascending.
## Visibility Semantics
- Non-admin users can view only `released` pages in non-admin-only drawers.
- Admin users can view all drawer-assigned pages according to current page status policy.
- Drawers with zero visible pages are hidden.
## Validation Artifacts
- `baseline_drawer_contract_validation.json`
- `baseline_drawer_visibility.json`

View File

@@ -0,0 +1,44 @@
# `frame_id` / `tool_src` Deprecation Plan
## Status
- Retirement completed in this change.
- Runtime navigation payload generation no longer emits:
- `frame_id`
- `tool_src`
## Context
Frame-era fields were used for iframe loading compatibility:
- `frame_id`
- `tool_src`
## Policy
Deprecation is phased and must not break active routes.
## Phases
1. **Compatibility phase**:
- Keep fields in payload.
- Ensure new router navigation logic does not rely on these fields.
2. **Dual-run phase**:
- Validate all navigation paths without frame fields.
3. **Retirement readiness**:
- Wrapper-first pages are stable in shell.
- Cutover gates G1~G7 are green in rehearsal.
4. **Removal phase**:
- Remove generation and downstream usage of `frame_id/tool_src`.
- Update related tests and docs.
## Removal Checkpoints
- Checkpoint A: drawer parity stable in canary.
- Checkpoint B: legacy wrappers stable with no frame-field dependency.
- Checkpoint C: rollback mechanism verified independent of frame fields. ✓
## Risk Controls
- Keep rollback-safe path via route-level navigation and kill-switch.
- Keep gate coverage for route/drawer/workflow parity after field removal.

View File

@@ -0,0 +1,40 @@
# Legacy Rewrite Playbook (Batch-2)
## Target Pages
This playbook governs rewrite execution for the remaining three legacy pages:
- `/job-query`
- `/excel-query`
- `/query-tool`
Rewrite order follows `legacy_rewrite_priority_matrix.md`.
## Canonical Steps
1. Preserve route and API contracts first.
2. Move page state and API calls into composables (`use<Page>Data`).
3. Replace page-local repeated blocks with shared UI components where possible.
4. Keep Tailwind token alignment for new/changed UI.
5. Validate with per-page smoke checklist before/after switch.
## Required Acceptance (Per Page)
- Route reachable and functional without shell wrapper.
- Core query workflow succeeds and returns expected result sections.
- Export path remains usable (where applicable).
- No new unhandled runtime error on the primary path.
- Checklist IDs pass:
- `/job-query`: `JOB-SMOKE-01`~`JOB-SMOKE-06`
- `/excel-query`: `EXCEL-SMOKE-01`~`EXCEL-SMOKE-06`
- `/query-tool`: `QTOOL-SMOKE-01`~`QTOOL-SMOKE-06`
Checklist source:
- `docs/migration/portal-no-iframe/legacy_rewrite_smoke_checklists.md`
## Shared Guardrails
- Do not change backend API signatures in rewrite phase.
- Keep direct-link behavior and query semantics stable.
- If parity fails, rollback to previous stable page artifact before continuing.

View File

@@ -0,0 +1,40 @@
# Legacy Rewrite Priority Matrix
## Scoring model
`priority_score = usage(0-5)*0.3 + complexity(0-5)*0.4 + risk(0-5)*0.3`
- Usage: current observed operational usage + business criticality.
- Complexity: route/API count, frontend LOC, workflow branches.
- Risk: data mutation/export/upload sensitivity and regression blast radius.
## Measured technical baseline
| Page | Backend route LOC | Template LOC | Frontend LOC | API surface |
| --- | ---: | ---: | ---: | --- |
| `query-tool` | 509 | 1267 | 3139 | resolve/history/adjacent/associations/equipment/export |
| `excel-query` | 355 | 1181 | 624 | upload/schema/query/export |
| `job-query` | 195 | 995 | 520 | resources/jobs/txn/export |
| `tmtt-defect` | 82 | 271 | 363 | analysis/query + CSV export |
## Priority scoring
| Page | Usage | Complexity | Risk | Score |
| --- | ---: | ---: | ---: | ---: |
| `tmtt-defect` | 2 | 1 | 2 | 1.6 |
| `job-query` | 3 | 2 | 3 | 2.6 |
| `excel-query` | 3 | 4 | 4 | 3.7 |
| `query-tool` | 4 | 5 | 5 | 4.7 |
## Rewrite order decision
1. `tmtt-defect` (canonical exemplar)
2. `job-query`
3. `excel-query`
4. `query-tool`
## Rationale
- Start with lowest-complexity page to establish shared migration playbook.
- Keep high-complexity/high-risk `query-tool` last to maximize reuse from prior rewrites.
- Defer upload-heavy `excel-query` until shared error/retry/upload patterns are stabilized.

View File

@@ -0,0 +1,77 @@
# Legacy Rewrite Smoke Checklists (Per-Page)
本文件是 `7.2 ~ 7.4` 的執行前置與驗收基準。
每一頁在「重寫前(wrapper baseline)」與「重寫後(rewrite candidate)」都必須執行同一組 smoke。
## 0. 執行規則
- 必須記錄:執行日期、分支/commit、執行人、環境(DEV/UAT)。
- 每頁 smoke 通過率要求:`100%`
- 任何 P0 smoke 失敗即視為 `No-Go`,不得進入 wrapper 移除。
- `excel-query``query-tool` 為 admin/dev 可見頁,需使用 admin 身份執行。
## 1. `tmtt-defect`Rewrite Exemplar
### 前置條件
- 可取得有效 `start_date/end_date` 測試區間。
- `/api/tmtt-defect/analysis``/api/tmtt-defect/export` 可連線。
### Smoke Cases
- [ ] `TMTT-SMOKE-01` Route reachable: `/tmtt-defect` 可直接開啟,無白屏/JS error。
- [ ] `TMTT-SMOKE-02` Required params guard: 缺少日期時,顯示明確錯誤且不崩潰。
- [ ] `TMTT-SMOKE-03` Query success: 送出合法日期後KPI/Charts/Detail 皆成功渲染。
- [ ] `TMTT-SMOKE-04` Drill-down: 點擊 Pareto 圖欄位可套用/清除篩選,明細同步。
- [ ] `TMTT-SMOKE-05` Table behavior: 明細表格可排序,排序方向切換正確。
- [ ] `TMTT-SMOKE-06` Export CSV: 匯出成功response 為 CSV 且檔名包含日期區間。
## 2. `job-query`
### 前置條件
- `resource` 清單可取得。
- 至少有一組可查詢日期區間。
### Smoke Cases
- [ ] `JOB-SMOKE-01` Route reachable: `/job-query` 可直接開啟。
- [ ] `JOB-SMOKE-02` Resource loading: `/api/job-query/resources` 回傳清單UI 可選取。
- [ ] `JOB-SMOKE-03` Query jobs: 選設備+日期後可查詢成功並顯示結果。
- [ ] `JOB-SMOKE-04` Txn detail: 由查詢結果可開啟某筆 job 的 txn history。
- [ ] `JOB-SMOKE-05` Export CSV: 匯出成功且檔案可下載。
- [ ] `JOB-SMOKE-06` Validation: 缺日期/無設備/超過上限時回傳明確錯誤訊息。
## 3. `excel-query`Admin
### 前置條件
- 準備一份有效 `.xlsx` 測試檔。
- Admin session 已登入。
### Smoke Cases
- [ ] `EXCEL-SMOKE-01` Route/auth: `/excel-query` admin 可進入,非 admin 受保護。
- [ ] `EXCEL-SMOKE-02` Upload: 上傳有效 Excel 後可解析欄位與預覽。
- [ ] `EXCEL-SMOKE-03` Column detect: 欄位唯一值與型別偵測可正常運作。
- [ ] `EXCEL-SMOKE-04` Execute query: 標準查詢與進階查詢都可回傳資料。
- [ ] `EXCEL-SMOKE-05` Export CSV: 查詢結果可匯出 CSV。
- [ ] `EXCEL-SMOKE-06` Invalid file guard: 非 `.xls/.xlsx` 檔案被拒絕且回傳可讀錯誤。
## 4. `query-tool`Admin
### 前置條件
- Admin session 已登入。
- 可用測試 lot/equipment/date range。
### Smoke Cases
- [ ] `QTOOL-SMOKE-01` Route reachable: `/query-tool` 可開啟。
- [ ] `QTOOL-SMOKE-02` Resolve flow: lot_id/serial/work_order 至少一種解析成功。
- [ ] `QTOOL-SMOKE-03` History flow: lot history 可查詢並顯示。
- [ ] `QTOOL-SMOKE-04` Adjacent flow: adjacent lots 查詢可回傳。
- [ ] `QTOOL-SMOKE-05` Associations: materials/rejects/holds/splits/jobs 查詢可用。
- [ ] `QTOOL-SMOKE-06` Equipment period: status_hours/lots/materials/rejects/jobs 至少各成功一次。
- [ ] `QTOOL-SMOKE-07` Export CSV: 匯出可下載且欄位合理。
- [ ] `QTOOL-SMOKE-08` Validation: 缺參數、非法日期範圍會回傳可讀錯誤。
## 5. Exit Rule與 7.4 連動)
只有在下列條件全成立,才可移除 wrapper
- [ ] 四頁 rewrite smoke 全部通過。
- [ ]`legacy_wrapper_telemetry_contract.md` 對照error 率在門檻內。
- [ ]`parity_checklist.md` 的 Route/Workflow/API contract 檢查一致。

View File

@@ -0,0 +1,21 @@
# Legacy Wrapper Exit Criteria (Rewrite-ready)
A wrapped page is rewrite-ready only when all criteria are met.
## Functional readiness
1. Core workflows are documented with at least one deterministic smoke script per workflow.
2. Route/query contract is frozen and covered by contract tests.
3. Export/upload side effects (if any) are reproducible in test or staging.
## Technical readiness
1. Shared UI and composables can cover at least 70% of page scaffolding (filters, cards, table shell, pagination).
2. Required API payload key/type contract is stable for two consecutive releases.
3. Wrapper telemetry shows no unresolved high-severity navigation failures in the last release cycle.
## Operational readiness
1. Rollback path for rewritten page is documented and rehearsed.
2. Error budget and success threshold for canary are defined before rewrite starts.
3. Product owner confirms parity acceptance checklist for the target page.

View File

@@ -0,0 +1,40 @@
# Legacy Wrapper Telemetry Contract
## Status
- Retired after wrapper decommission.
- `POST /api/portal/wrapper-telemetry` has been removed.
- Reference only for historical migration traceability.
## Wrapper scope
- `/job-query`
- `/excel-query`
- `/query-tool`
- `/tmtt-defect`
## Client events
- `wrapper_loaded`: wrapper route rendered in shell.
- `launch`: user clicked "進入既有頁面" and navigation handoff started.
## API endpoint
- `POST /api/portal/wrapper-telemetry`
- Payload:
- `event_type: string`
- `route: string` (must be one of wrapper scope routes)
- `page_name?: string`
- `drawer_name?: string`
- `duration_ms?: number`
- `ts?: string`
## Validation
- Reject unknown routes with `400`.
- Reject missing `event_type` with `400`.
## Fallback behavior
- Wrapper UI always provides direct anchor navigation to the legacy route.
- Telemetry failure must not block navigation.

View File

@@ -0,0 +1,20 @@
# Motion Baseline Guidelines (Vue Transition First)
## Baseline principles
1. Motion clarifies state change, not decoration.
2. Default to short transitions (180ms - 240ms) with easing.
3. Keep animation on container level (route/panel/filter-chip), avoid animating large table row sets.
## Standard transitions
- Route change: `route-fade` (`opacity + translateY`) in portal shell.
- Drawer navigation: hover/active transition on sidebar links.
- Filter apply/remove: `TransitionGroup` chip enter/leave motion.
- Data refresh pulse: panel-level pulse when chart/table refresh is running.
## Accessibility
- Respect `prefers-reduced-motion: reduce`.
- All key transitions must have non-animated fallback styles.
- Motion must not block interaction or delay data rendering.

View File

@@ -0,0 +1,19 @@
# GSAP Escalation Rule
## Default
Use Vue native transitions and CSS transitions for portal migration work.
## GSAP allowed only when all conditions are true
1. Interaction cannot be expressed with native Vue/CSS transitions without major maintainability cost.
2. Animation is business-critical (e.g., complex timeline playback or synchronized multi-chart storytelling).
3. Reduced-motion fallback is explicitly implemented.
4. Performance impact is measured on target hardware and meets baseline thresholds.
5. A rollback switch exists to disable advanced animation without breaking functionality.
## Approval checklist
- Document the exact scenario and why Vue/CSS is insufficient.
- Add test coverage for degraded/non-animated path.
- Confirm bundle-size impact is acceptable for the target route.

View File

@@ -0,0 +1,27 @@
# Shared Pagination Migration Batch 1
## Scope
Migrated pages/components:
- `wip-detail/components/LotTable.vue`
- `hold-detail/components/LotTable.vue`
- `hold-overview/components/LotTable.vue`
- `hold-history/components/DetailTable.vue`
- `mid-section-defect/components/DetailTable.vue`
## Change
- Replaced direct/inline pagination rendering with shared `PaginationControl`.
- Preserved existing page event contracts (`prev-page`, `next-page`).
## Visual parity checks
- Pagination visibility still depends on `totalPages > 1`.
- Prev/Next button enablement remains bounded by page range.
- Page info text format remains unchanged on migrated views.
## Removed duplicated artifacts
- Removed local Prev/Next markup and boundary logic from `hold-history/components/DetailTable.vue`.
- Consolidated pagination behavior into shared wrapper for this batch.

View File

@@ -0,0 +1,46 @@
# Portal No-Iframe Migration Parity Checklist
This checklist is the execution companion for `portal-no-iframe-navigation` migration.
## A. Drawer Visibility Parity
- [ ] Non-admin visible drawers/routes match `baseline_drawer_visibility.json` exactly.
- [ ] Admin visible drawers/routes match `baseline_drawer_visibility.json` exactly.
- [ ] Empty drawers remain hidden.
- [ ] `admin_only` drawer behavior remains unchanged.
## B. Route and Query Contract Parity
- [ ] `/wip-overview` preserves `workorder|lotid|package|type|status` URL semantics.
- [ ] `/wip-detail` preserves `workcenter|workorder|lotid|package|type|status` URL semantics.
- [ ] `/hold-detail` preserves required `reason` semantics and fallback behavior.
- [ ] `/resource-history` preserves date/granularity/group/family/resource/flag query semantics.
## C. Core Workflow Smoke Paths
- [ ] Legacy rewrite per-page smoke checklist passes (`legacy_rewrite_smoke_checklists.md`).
- [ ] `/` open portal and switch via drawer navigation.
- [ ] `/wip-overview` apply filters and drill down to `/wip-detail`.
- [ ] `/wip-overview` reason drill-down to `/hold-detail`.
- [ ] `/resource-history` execute query and export path.
- [ ] Legacy rewrite pages (`/job-query`, `/excel-query`, `/query-tool`, `/tmtt-defect`) remain reachable and usable.
## D. API Payload Contract Parity
- [ ] `/api/wip/overview/summary` required keys present.
- [ ] `/api/wip/overview/matrix` required keys present.
- [ ] `/api/wip/hold-detail/summary` required keys present.
- [ ] `/api/resource/history/summary` required keys present.
- [ ] `/api/resource/history/detail` required keys present.
## E. Stability and Performance
- [ ] No unhandled JS runtime errors on critical E2E paths.
- [ ] Route switch latency remains within agreed threshold.
- [ ] Memory footprint does not regress beyond agreed threshold.
## F. Cutover Decision
- [ ] All G1~G7 gates are green.
- [ ] Rollback rehearsal result is recent and valid.
- [ ] Cutover owner and rollback owner are explicitly assigned.

View File

@@ -0,0 +1,21 @@
# Performance Baseline Comparison
Measured via Flask test client (route latency in ms).
## Key Entry Routes
| Surface | Avg (ms) | P95 (ms) |
| --- | ---: | ---: |
| Legacy portal `/` | 1.557 | 0.891 |
| SPA shell `/portal-shell` | 0.239 | 0.263 |
## Shared API Route
| Route | Legacy Avg (ms) | SPA Avg (ms) | Delta (ms) |
| --- | ---: | ---: | ---: |
| `/api/portal/navigation` | 0.341 | 0.313 | -0.028 |
## Notes
- This baseline is synthetic (test client), used for migration regression gate trend tracking.
- Production browser/network RUM should be captured separately during canary rollout.

View File

@@ -0,0 +1,61 @@
{
"portal_spa_enabled": false,
"samples_per_route": 15,
"metrics": [
{
"route": "/",
"samples": 15,
"avg_ms": 1.557,
"p95_ms": 0.891,
"min_ms": 0.536,
"max_ms": 14.997,
"status_codes": [
200
]
},
{
"route": "/api/portal/navigation",
"samples": 15,
"avg_ms": 0.341,
"p95_ms": 0.396,
"min_ms": 0.284,
"max_ms": 0.404,
"status_codes": [
200
]
},
{
"route": "/wip-overview",
"samples": 15,
"avg_ms": 0.683,
"p95_ms": 0.925,
"min_ms": 0.422,
"max_ms": 2.633,
"status_codes": [
200
]
},
{
"route": "/resource",
"samples": 15,
"avg_ms": 0.413,
"p95_ms": 0.506,
"min_ms": 0.337,
"max_ms": 0.699,
"status_codes": [
200
]
},
{
"route": "/qc-gate",
"samples": 15,
"avg_ms": 0.422,
"p95_ms": 0.453,
"min_ms": 0.371,
"max_ms": 0.615,
"status_codes": [
200
]
}
]
}

View File

@@ -0,0 +1,72 @@
{
"portal_spa_enabled": true,
"samples_per_route": 15,
"metrics": [
{
"route": "/portal-shell",
"samples": 15,
"avg_ms": 0.239,
"p95_ms": 0.263,
"min_ms": 0.169,
"max_ms": 0.708,
"status_codes": [
200
]
},
{
"route": "/api/portal/navigation",
"samples": 15,
"avg_ms": 0.313,
"p95_ms": 0.412,
"min_ms": 0.257,
"max_ms": 0.437,
"status_codes": [
200
]
},
{
"route": "/job-query",
"samples": 15,
"avg_ms": 0.904,
"p95_ms": 0.786,
"min_ms": 0.33,
"max_ms": 7.345,
"status_codes": [
200
]
},
{
"route": "/excel-query",
"samples": 15,
"avg_ms": 0.47,
"p95_ms": 0.448,
"min_ms": 0.324,
"max_ms": 1.951,
"status_codes": [
403
]
},
{
"route": "/query-tool",
"samples": 15,
"avg_ms": 0.448,
"p95_ms": 0.802,
"min_ms": 0.32,
"max_ms": 0.849,
"status_codes": [
403
]
},
{
"route": "/tmtt-defect",
"samples": 15,
"avg_ms": 0.583,
"p95_ms": 0.585,
"min_ms": 0.323,
"max_ms": 3.455,
"status_codes": [
200
]
}
]
}

View File

@@ -0,0 +1,51 @@
# Portal No-Iframe Migration Rollback Rehearsal Runbook
## Objective
Validate that navigation can be restored to pre-cutover stable behavior within target SLO.
- Target recovery SLO: <= 15 minutes
## Trigger Conditions
Execute rollback when any of the following occur after cutover:
- P0 route unavailable or broken workflow.
- Drawer visibility parity mismatch.
- Critical API payload contract mismatch causing page failure.
- Severe runtime JS errors on critical user paths.
## Preconditions
- Feature-flag/env toggle path for shell cutover is in place.
- Latest baseline snapshots are available under `docs/migration/portal-no-iframe/`.
- On-call owner and rollback owner are assigned.
## Rehearsal Steps
1. Enable new navigation mode in staging/canary.
2. Execute parity checklist (`parity_checklist.md`) on critical routes.
3. Force simulated rollback trigger (toggle off new mode).
4. Re-run critical smoke checks:
- portal load
- drawer visibility
- wip overview/detail flow
- resource history query path
5. Record elapsed recovery time and failures.
## Verification Criteria
- Toggle change takes effect without manual code rollback.
- Critical routes recover to expected behavior.
- Recovery time is within SLO.
- No residual hard-failure state remains.
## Post-Rehearsal Record
- Date:
- Environment:
- Operator:
- Trigger reason:
- Recovery duration:
- Issues found:
- Follow-up actions:

View File

@@ -0,0 +1,28 @@
# Rollback Strategy (Shell / Router / Wrapper)
## Scope
- Shell entry failures (`/portal-shell`, route guards, navigation API)
- Legacy route integration failures (`job-query`, `excel-query`, `query-tool`, `tmtt-defect`)
## Immediate actions
1. Flip `PORTAL_SPA_ENABLED=false`.
2. Confirm `/` portal route responds and sidebar route links render.
3. Verify core routes (`/wip-overview`, `/resource`, `/qc-gate`) return 2xx.
4. Verify legacy routes (`/job-query`, `/excel-query`, `/query-tool`, `/tmtt-defect`) return 2xx.
## Validation checkpoints (<=15 minutes)
- `GET /api/portal/navigation` returns deterministic drawer/page list.
- No spike in 5xx for portal and legacy routes.
- Smoke flows for one P0 page and one legacy page pass.
## Legacy route fallback
- If a legacy route fails hard, temporarily hide it from drawer config (`page_status.json`) and announce maintenance route.
## Post-rollback follow-up
- Capture failed gate(s), timestamp, and impacted routes.
- Generate incident summary with fix candidate and rehearsal re-entry criteria.

View File

@@ -0,0 +1,27 @@
# Phased Rollout Plan (Canary)
## Feature switch
- Primary switch: `PORTAL_SPA_ENABLED`
- Canary enabled users/groups are routed to `/portal-shell`; others remain on `/` route-based portal.
## Phases
1. Phase A (Internal): dev/admin users only, 1 day.
2. Phase B (Canary): 10-20% target users, 2-3 days.
3. Phase C (Broad): 50% users if gates are green for 24h.
4. Phase D (Full): 100% after cutover gates pass.
## Success thresholds
- Route availability (P0): >= 99.9% 2xx/3xx.
- Client runtime error rate on critical paths: 0 unhandled exceptions.
- Drawer parity drift: 0 mismatches (admin/non-admin route sets).
- Wrapper launch success (`launch` telemetry): >= 99%.
## Error thresholds (rollback trigger)
- P0 route availability < 99.5% in any 30-minute window.
- Any critical workflow smoke failure.
- Drawer parity mismatch count > 0 after deployment.
- Wrapper telemetry error rate >= 2% sustained 30 minutes.

View File

@@ -0,0 +1,50 @@
# Shared UI Component Contracts
## `PaginationControl`
File: `frontend/src/shared-ui/components/PaginationControl.vue`
- Props:
- `page?: number` (legacy compatibility)
- `modelValue?: number`
- `totalPages: number`
- `infoText?: string`
- `visible?: boolean`
- Emits:
- `update:modelValue(number)`
- `change(number)`
- `prev(number)`
- `next(number)`
- Compatibility:
- Supports legacy usage (`:page`, `@prev`, `@next`) for migration-safe replacement.
## `SectionCard`
File: `frontend/src/shared-ui/components/SectionCard.vue`
- Slots:
- `header`
- default body
- `footer`
- Purpose:
- Normalize page section container structure and spacing.
## `FilterToolbar`
File: `frontend/src/shared-ui/components/FilterToolbar.vue`
- Slots:
- default filter controls
- `actions`
- Purpose:
- Shared filter layout shell with consistent spacing and action alignment.
## `StatusBadge`
File: `frontend/src/shared-ui/components/StatusBadge.vue`
- Props:
- `tone: neutral | success | warning | danger`
- `text: string`
- Purpose:
- Replace repeated local badge/status color snippets.

View File

@@ -0,0 +1,31 @@
# Shared Composables Contracts
## `useAutoRefresh`
File: `frontend/src/shared-composables/useAutoRefresh.js`
- Current behavior wraps existing `wip-shared` implementation.
- Purpose: single import path for all page modules before deeper implementation merge.
## `useAutocomplete`
File: `frontend/src/shared-composables/useAutocomplete.js`
- Current behavior wraps existing `wip-shared` implementation.
- Purpose: single import path to normalize field/autocomplete interactions.
## `usePaginationState`
File: `frontend/src/shared-composables/usePaginationState.js`
- State: `page`, `perPage`, `total`, `totalPages`
- Derived: `hasPrev`, `hasNext`
- Methods: `setFromPayload`, `reset`
## `useQueryState`
File: `frontend/src/shared-composables/useQueryState.js`
- `readQueryState(keys)`
- `writeQueryState(nextState)`
- Purpose: unify URL query read/write semantics across pages.

View File

@@ -0,0 +1,40 @@
# Tailwind Design Tokens Mapping
## Goal
Map existing portal visual language into a stable token set for phased migration.
## Color tokens
- `brand.500` / `brand.600` / `brand.700`: primary brand actions and active navigation states.
- `accent.500`: gradient accent endpoint for shell headers.
- `surface.app` / `surface.card` / `surface.muted`: app background, card surfaces, muted blocks.
- `stroke.soft` / `stroke.panel`: border hierarchy.
- `state.success` / `state.warning` / `state.danger` / `state.neutral`: status dots and health states.
## Typography tokens
- `fontFamily.sans`: `Noto Sans TC`, `Microsoft JhengHei`, system fallback.
## Layout tokens
- `spacing.shell`: outer shell padding.
- `spacing.panel`: panel interior spacing.
- `spacing.nav`: sidebar item horizontal spacing.
- `spacing.block`: vertical rhythm baseline.
## Radius and elevation tokens
- `borderRadius.shell`: shell and main card radius.
- `borderRadius.card`: smaller control/card radius.
- `boxShadow.soft`: light containers (sidebar).
- `boxShadow.panel`: content panel container.
- `boxShadow.shell`: header gradient card emphasis.
## Z-index token
- `zIndex.popup`: status popup / overlay layer.
## Migration note
Tokens are intentionally aligned with current portal values to minimize visual drift during iframe decommission.

View File

@@ -0,0 +1,33 @@
# Tailwind Migration Guide (Portal No-iframe)
## Purpose
Move distributed page CSS toward a token-driven Tailwind system without breaking existing portal behavior.
## Step-by-step
1. Keep existing route/page behavior unchanged.
2. Replace repeated layout wrappers with Tailwind utilities first (`grid`, `flex`, spacing, radius, shadows).
3. Replace repeated visual primitives with shared component classes from `@layer components`.
4. Move hard-coded colors/spacing to tokens in `tailwind.config.js` and `tailwind.css`.
5. Remove obsolete page-local CSS only after visual parity is verified.
## Recommended migration order
1. Shell and shared navigation blocks
2. Filter bars and KPI card rows
3. Shared table containers and pagination controls
4. Page-specific edge states and empty/error banners
## Parity checks per batch
- Drawer visibility and route links stay unchanged.
- Existing URL/query semantics remain compatible.
- No new runtime style conflicts in non-admin/admin views.
## Do / Dont
- Do: prefer composable utility classes and shared Vue components.
- Do: keep style changes scoped to one route family per batch.
- Dont: introduce new long inline `<style>` blocks in templates.
- Dont: mix unrelated refactors with migration styling tasks.

View File

@@ -0,0 +1,26 @@
# Tailwind Style Governance (Migration Phase)
## Scope
- Applies to all new frontend work under `frontend/src/**` during iframe removal migration.
- Existing page-local CSS can remain temporarily, but new large page-local blocks are disallowed.
## Rules
1. New shared UI styles must be authored in Tailwind layers (`base`, `components`, `utilities`) under `frontend/src/styles/tailwind.css`.
2. Reusable patterns (cards, filter bars, badge groups, table shells) must use component classes or Vue components, not copy-pasted CSS.
3. Page-specific CSS additions over 40 lines require an explicit migration note in the PR and an issue to move them into shared layers.
4. Token values must come from `tailwind.config.js` or CSS variables in `tailwind.css`; hard-coded new color scales are disallowed.
5. Motion/accessibility styles must support reduced-motion fallback and avoid forced animation on critical data refresh paths.
## Review Checklist
- New files import `frontend/src/styles/tailwind.css` through the entry module.
- No new iframe-targeting selectors are introduced.
- Shared classes/components are reused before adding page-local CSS.
- Token naming remains stable (`brand`, `surface`, `stroke`, `state`, spacing/radius/shadow/z-index).
## Exceptions
- Bugfix hotfixes may temporarily bypass these rules only if release risk is high.
- Every exception must include an expiry task in `openspec/changes/portal-no-iframe-navigation/tasks.md`.

View File

@@ -0,0 +1,44 @@
# `tmtt-defect` Rewrite Exemplar
## Scope
- Route: `/tmtt-defect`
- Goal: establish the first canonical legacy rewrite pattern with:
- Vue SFC composition
- shared UI layer reuse
- Tailwind token layer coexistence
- no iframe / no wrapper dependency
## Implemented Structure
- Entry: `frontend/src/tmtt-defect/main.js`
- Page container: `frontend/src/tmtt-defect/App.vue`
- Data state/composable: `frontend/src/tmtt-defect/composables/useTmttDefectData.js`
- Reusable page components:
- `frontend/src/tmtt-defect/components/TmttKpiCards.vue`
- `frontend/src/tmtt-defect/components/TmttChartCard.vue`
- `frontend/src/tmtt-defect/components/TmttDetailTable.vue`
- Shared UI usage:
- `frontend/src/shared-ui/components/FilterToolbar.vue`
- `frontend/src/shared-ui/components/SectionCard.vue`
- `frontend/src/shared-ui/components/StatusBadge.vue`
- Backend template mount shell: `src/mes_dashboard/templates/tmtt_defect.html`
## Behavioral Parity
The rewrite keeps current route and API contracts:
- Query API: `GET /api/tmtt-defect/analysis`
- Export API: `GET /api/tmtt-defect/export`
- Sort/filter/detail behavior preserved on result table
Smoke coverage references:
- `TMTT-SMOKE-01` ~ `TMTT-SMOKE-06` in `legacy_rewrite_smoke_checklists.md`
## Verification Snapshot
- `npm --prefix frontend run build` passed
- `pytest -q tests/test_template_integration.py tests/test_portal_shell_routes.py tests/test_cutover_gates.py tests/test_app_factory.py` passed
This page is the baseline implementation that remaining legacy rewrites follow.

View File

@@ -0,0 +1,41 @@
# UI Pattern Inventory (WIP / Resource / Hold / QC)
## Duplicated patterns observed
1. Filter bars:
- `hold-overview/components/FilterBar.vue`
- `hold-history/components/FilterBar.vue`
- `resource-status/components/FilterBar.vue`
- `resource-history/components/FilterBar.vue`
- `mid-section-defect/components/FilterBar.vue`
2. KPI/Summary cards:
- `wip-overview/components/SummaryCards.vue`
- `wip-detail/components/SummaryCards.vue`
- `hold-detail/components/SummaryCards.vue`
- `hold-history/components/SummaryCards.vue`
- `resource-status/components/SummaryCards.vue`
- `resource-history/components/KpiCards.vue`
- `mid-section-defect/components/KpiCards.vue`
3. Table + pagination shells:
- `wip-detail/components/LotTable.vue`
- `hold-detail/components/LotTable.vue`
- `hold-overview/components/LotTable.vue`
- `hold-history/components/DetailTable.vue`
- `mid-section-defect/components/DetailTable.vue`
- `qc-gate/components/LotTable.vue`
4. Multi-select and query controls:
- `resource-shared/components/MultiSelect.vue`
- `mid-section-defect/components/MultiSelect.vue`
5. Repeated status/badge presentation logic:
- WIP/Hold status class mapping and local badge styles in multiple tables/cards.
## Consolidation targets
- Shared UI layer (`frontend/src/shared-ui/components`)
- Shared composables layer (`frontend/src/shared-composables`)
- Tailwind tokenized styles (`frontend/src/styles/tailwind.css`)
## First migration batch completed
- Unified pagination rendering for WIP/Hold/Mid-section detail tables through `PaginationControl` wrapper.
- Auto-refresh and autocomplete imports migrated to `shared-composables` entry points.

View File

@@ -0,0 +1,27 @@
# Wrapper Decommission Report
## Decision
Legacy shell wrapper mode has been decommissioned after rewrite milestone validation.
## Changes Applied
- Removed shell wrapper route branch:
- `frontend/src/portal-shell/router.js`
- `frontend/src/portal-shell/App.vue`
- Removed wrapper-specific frontend artifacts:
- deleted `frontend/src/portal-shell/constants.js`
- deleted `frontend/src/portal-shell/views/LegacyWrapperView.vue`
- Removed backend wrapper telemetry endpoint:
- deleted `/api/portal/wrapper-telemetry` in `src/mes_dashboard/app.py`
## Operational Outcome
- Portal shell navigation now uses direct page-bridge behavior only.
- Legacy page access remains available via direct routes.
- Wrapper telemetry contract is retired.
## Validation
- Route and template integration tests updated and passing.
- Cutover gate tests remain green after wrapper removal.