From c38b5f646a46239b1701cac82a6ab30463088336 Mon Sep 17 00:00:00 2001 From: egg Date: Thu, 12 Feb 2026 13:20:06 +0800 Subject: [PATCH] feat(modernization): promote deferred routes to in-scope and unify page header styles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Promote /tables, /excel-query, /query-tool, /mid-section-defect from deferred to full shell-governed in-scope routes with canonical redirects, content contracts, governance artifacts, and updated CI gates. Unify all page header gradients to #667eea → #764ba2 and h1 font-size to 24px for visual consistency across all dashboard pages. Remove Native Route-View dev annotations from job-query, excel-query, and query-tool headers. Co-Authored-By: Claude Opus 4.6 --- .env.example | 10 ++ .../workflows/full-modernization-gates.yml | 14 +++ .../README.md | 27 +++++ .../exception_registry.json | 42 ++++++++ .../governance_milestones.md | 39 +++++++ .../observability_checkpoints.md | 44 ++++++++ .../pre_change_confirmation_template.md | 35 ++++++ .../pre_change_confirmations.json | 85 +++++++++++++++ .../rollback_controls.md | 31 ++++++ .../rollout_runbook.md | 47 ++++++++ .../route_scope_matrix.json | 100 ++++++++++++++++++ .../scope_boundary_note.md | 30 ++++++ .../upstream_linkage.json | 27 +++++ .../asset_readiness_manifest.json | 13 ++- .../canonical_routing_policy.json | 6 +- .../exception_registry.json | 24 +++++ .../interaction_parity_checks.json | 6 +- .../known_bug_baseline.json | 18 +++- .../parity_golden_fixtures.json | 6 +- .../quality_gate_policy.json | 7 +- .../quality_gate_report.json | 5 +- .../route_content_contracts.json | 24 +++++ .../route_contracts.json | 8 +- .../route_scope_matrix.json | 35 ++++-- .../style_inventory.json | 6 +- .../visual-regression-snapshots.json | 10 +- frontend/src/excel-query/App.vue | 1 - frontend/src/excel-query/style.css | 2 +- frontend/src/hold-detail/style.css | 2 +- frontend/src/hold-history/style.css | 4 +- frontend/src/hold-overview/style.css | 2 +- frontend/src/job-query/App.vue | 1 - frontend/src/job-query/style.css | 4 +- frontend/src/mid-section-defect/style.css | 2 +- frontend/src/portal-shell/routeContracts.js | 27 +++-- frontend/src/qc-gate/style.css | 6 +- frontend/src/query-tool/App.vue | 1 - frontend/src/query-tool/style.css | 2 +- frontend/src/resource-history/style.css | 2 +- frontend/src/resource-shared/styles.css | 2 +- frontend/src/tables/style.css | 4 +- frontend/src/tmtt-defect/style.css | 4 +- ...al-shell-route-contract-governance.test.js | 16 +-- gunicorn.conf.py | 14 +-- .../.openspec.yaml | 0 .../design.md | 16 ++- .../proposal.md | 2 + .../spec.md | 0 .../page-content-modernization-safety/spec.md | 17 ++- .../unified-shell-route-coverage/spec.md | 0 .../tasks.md | 41 +++++++ .../tasks.md | 38 ------- .../spec.md | 21 ++-- .../page-content-modernization-safety/spec.md | 26 ++++- .../unified-shell-route-coverage/spec.md | 14 +-- scripts/check_full_modernization_gates.py | 8 +- src/mes_dashboard/app.py | 28 ++++- src/mes_dashboard/routes/query_tool_routes.py | 4 + tests/e2e/test_admin_auth_e2e.py | 88 +++++++++------ tests/e2e/test_resource_history_e2e.py | 61 ++++++++--- tests/stress/test_api_load.py | 42 ++++---- tests/test_asset_readiness_policy.py | 22 +++- tests/test_full_modernization_gates.py | 7 +- tests/test_portal_shell_routes.py | 19 +++- .../test_portal_shell_wave_b_native_smoke.py | 25 +++-- tests/test_query_tool_routes.py | 24 +++-- tests/test_template_integration.py | 27 +++-- 67 files changed, 1073 insertions(+), 252 deletions(-) create mode 100644 docs/migration/deferred-route-modernization-follow-up/README.md create mode 100644 docs/migration/deferred-route-modernization-follow-up/exception_registry.json create mode 100644 docs/migration/deferred-route-modernization-follow-up/governance_milestones.md create mode 100644 docs/migration/deferred-route-modernization-follow-up/observability_checkpoints.md create mode 100644 docs/migration/deferred-route-modernization-follow-up/pre_change_confirmation_template.md create mode 100644 docs/migration/deferred-route-modernization-follow-up/pre_change_confirmations.json create mode 100644 docs/migration/deferred-route-modernization-follow-up/rollback_controls.md create mode 100644 docs/migration/deferred-route-modernization-follow-up/rollout_runbook.md create mode 100644 docs/migration/deferred-route-modernization-follow-up/route_scope_matrix.json create mode 100644 docs/migration/deferred-route-modernization-follow-up/scope_boundary_note.md create mode 100644 docs/migration/deferred-route-modernization-follow-up/upstream_linkage.json rename openspec/changes/{deferred-route-modernization-follow-up => archive/2026-02-12-deferred-route-modernization-follow-up}/.openspec.yaml (100%) rename openspec/changes/{deferred-route-modernization-follow-up => archive/2026-02-12-deferred-route-modernization-follow-up}/design.md (72%) rename openspec/changes/{deferred-route-modernization-follow-up => archive/2026-02-12-deferred-route-modernization-follow-up}/proposal.md (85%) rename openspec/changes/{deferred-route-modernization-follow-up => archive/2026-02-12-deferred-route-modernization-follow-up}/specs/asset-readiness-and-fallback-retirement/spec.md (100%) rename openspec/changes/{deferred-route-modernization-follow-up => archive/2026-02-12-deferred-route-modernization-follow-up}/specs/page-content-modernization-safety/spec.md (56%) rename openspec/changes/{deferred-route-modernization-follow-up => archive/2026-02-12-deferred-route-modernization-follow-up}/specs/unified-shell-route-coverage/spec.md (100%) create mode 100644 openspec/changes/archive/2026-02-12-deferred-route-modernization-follow-up/tasks.md delete mode 100644 openspec/changes/deferred-route-modernization-follow-up/tasks.md diff --git a/.env.example b/.env.example index e4a75bc..028f494 100644 --- a/.env.example +++ b/.env.example @@ -72,6 +72,16 @@ GUNICORN_WORKERS=2 # Threads per worker GUNICORN_THREADS=4 +# Worker timeout (seconds): should stay above DB/query-tool slow paths +GUNICORN_TIMEOUT=130 + +# Graceful shutdown timeout for worker reloads (seconds) +GUNICORN_GRACEFUL_TIMEOUT=60 + +# Worker recycle policy (set 0 to disable) +GUNICORN_MAX_REQUESTS=5000 +GUNICORN_MAX_REQUESTS_JITTER=500 + # ============================================================ # Redis Configuration (for WIP cache) # ============================================================ diff --git a/.github/workflows/full-modernization-gates.yml b/.github/workflows/full-modernization-gates.yml index 114faf9..956b04b 100644 --- a/.github/workflows/full-modernization-gates.yml +++ b/.github/workflows/full-modernization-gates.yml @@ -5,6 +5,10 @@ on: paths: - "frontend/src/portal-shell/**" - "frontend/src/qc-gate/**" + - "frontend/src/tables/**" + - "frontend/src/excel-query/**" + - "frontend/src/query-tool/**" + - "frontend/src/mid-section-defect/**" - "frontend/tests/portal-shell-*.test.js" - "src/mes_dashboard/**" - "tests/test_portal_shell_routes.py" @@ -12,13 +16,20 @@ on: - "tests/test_asset_readiness_policy.py" - "scripts/check_full_modernization_gates.py" - "docs/migration/full-modernization-architecture-blueprint/**" + - "docs/migration/deferred-route-modernization-follow-up/**" - "openspec/changes/full-modernization-architecture-blueprint/**" + - "openspec/changes/deferred-route-modernization-follow-up/**" + - "data/modernization_feature_flags.json" - ".github/workflows/full-modernization-gates.yml" push: branches: [ main ] paths: - "frontend/src/portal-shell/**" - "frontend/src/qc-gate/**" + - "frontend/src/tables/**" + - "frontend/src/excel-query/**" + - "frontend/src/query-tool/**" + - "frontend/src/mid-section-defect/**" - "frontend/tests/portal-shell-*.test.js" - "src/mes_dashboard/**" - "tests/test_portal_shell_routes.py" @@ -26,7 +37,10 @@ on: - "tests/test_asset_readiness_policy.py" - "scripts/check_full_modernization_gates.py" - "docs/migration/full-modernization-architecture-blueprint/**" + - "docs/migration/deferred-route-modernization-follow-up/**" - "openspec/changes/full-modernization-architecture-blueprint/**" + - "openspec/changes/deferred-route-modernization-follow-up/**" + - "data/modernization_feature_flags.json" - ".github/workflows/full-modernization-gates.yml" jobs: diff --git a/docs/migration/deferred-route-modernization-follow-up/README.md b/docs/migration/deferred-route-modernization-follow-up/README.md new file mode 100644 index 0000000..3fb0883 --- /dev/null +++ b/docs/migration/deferred-route-modernization-follow-up/README.md @@ -0,0 +1,27 @@ +# Deferred Route Modernization Follow-up Artifacts + +This directory stores execution artifacts for `deferred-route-modernization-follow-up`. + +## Upstream Reference + +- Phase 1: `docs/migration/full-modernization-architecture-blueprint/` +- Handoff: `docs/migration/full-modernization-architecture-blueprint/deferred_route_handoff.md` + +## Core Governance + +- `route_scope_matrix.json`: frozen in-scope deferred route matrix (promoted from phase 1 deferred). +- `governance_milestones.md`: completion and deprecation milestones for deferred-route phase. +- `exception_registry.json`: approved temporary exceptions with owner and milestone. +- `upstream_linkage.json`: explicit linkage to phase 1 handoff artifacts. +- `scope_boundary_note.md`: clarification that dev routes are eligible for modernization. + +## Pre-Change Confirmation + +- `pre_change_confirmation_template.md`: required fields and template. +- `pre_change_confirmations.json`: recorded per-route confirmations. + +## Rollout Operations + +- `rollout_runbook.md`: phase steps and hold points for deferred-route cutover. +- `rollback_controls.md`: per-route rollback and false-positive gate handling. +- `observability_checkpoints.md`: route/gate/rollback observability contract. diff --git a/docs/migration/deferred-route-modernization-follow-up/exception_registry.json b/docs/migration/deferred-route-modernization-follow-up/exception_registry.json new file mode 100644 index 0000000..65efa8f --- /dev/null +++ b/docs/migration/deferred-route-modernization-follow-up/exception_registry.json @@ -0,0 +1,42 @@ +{ + "version": 1, + "change": "deferred-route-modernization-follow-up", + "fields": [ + "id", + "type", + "scope", + "owner", + "introduced_by", + "reason", + "mitigation", + "status", + "milestone", + "tracking_issue" + ], + "entries": [ + { + "id": "style-excel-query-shell-tokens-no-fallback", + "type": "style", + "scope": "/excel-query", + "owner": "frontend-mes-reporting", + "introduced_by": "legacy-template", + "reason": "excel-query uses shell tokens (--portal-brand-start, --portal-brand-end, --portal-shadow-panel) without CSS fallback values; inherited from legacy era before shell token governance", + "mitigation": "add fallback values during content modernization cutover for /excel-query", + "status": "approved-temporary", + "milestone": "2026-03-19", + "tracking_issue": "deferred-route-modernization-follow-up/excel-query-style-hardening" + }, + { + "id": "style-query-tool-shell-tokens-no-fallback", + "type": "style", + "scope": "/query-tool", + "owner": "frontend-mes-reporting", + "introduced_by": "legacy-template", + "reason": "query-tool uses shell tokens (--portal-brand-start, --portal-brand-end, --portal-shadow-panel) without CSS fallback values; inherited from legacy era before shell token governance", + "mitigation": "add fallback values during content modernization cutover for /query-tool", + "status": "approved-temporary", + "milestone": "2026-03-19", + "tracking_issue": "deferred-route-modernization-follow-up/query-tool-style-hardening" + } + ] +} diff --git a/docs/migration/deferred-route-modernization-follow-up/governance_milestones.md b/docs/migration/deferred-route-modernization-follow-up/governance_milestones.md new file mode 100644 index 0000000..8cebbde --- /dev/null +++ b/docs/migration/deferred-route-modernization-follow-up/governance_milestones.md @@ -0,0 +1,39 @@ +# Deferred Route Modernization Governance Milestones + +## Upstream Reference + +- Phase 1: `full-modernization-architecture-blueprint` +- Handoff: `docs/migration/full-modernization-architecture-blueprint/deferred_route_handoff.md` + +## Phase Completion Criteria + +A phase is complete only when all criteria below are true: + +1. Route governance: 100% of in-scope deferred routes in `route_scope_matrix.json` have valid shell contract metadata and ownership with scope promoted to `in-scope`. +2. Style governance: deferred route-local styles do not introduce page-global selectors (`:root`, `body`) unless recorded in exception registry. +3. Quality governance: functional parity, visual checkpoints, accessibility checks, and performance budgets pass at configured gate severity. +4. Content safety governance: page-content parity evidence + manual acceptance sign-off exist for each migrated deferred route. +5. Bug carry-over governance: known-bug replay checks for migrated scope do not reproduce legacy defects. +6. Pre-change confirmation: each deferred route has an approved pre-change confirmation record before implementation begins. + +## Legacy Deprecation Milestones + +- 2026-02-19: deferred route contract CI completeness gate enabled in `warn` mode. +- 2026-02-26: deferred route contract CI completeness gate promoted to `block` mode. +- 2026-03-05: deferred route asset readiness gate promoted to `block` mode. +- 2026-03-12: runtime fallback posture retired for deferred routes in production policy. +- 2026-03-19: unresolved style exceptions past milestone fail modernization review. + +## Route Cutover Sequence + +Routes are cut over one at a time in the following planned order: + +1. `/tables` +2. `/excel-query` +3. `/query-tool` +4. `/mid-section-defect` + +Next route cutover is blocked until current route has: +- Parity pass (golden fixtures + interaction checks) +- Manual acceptance sign-off +- Known-bug replay pass diff --git a/docs/migration/deferred-route-modernization-follow-up/observability_checkpoints.md b/docs/migration/deferred-route-modernization-follow-up/observability_checkpoints.md new file mode 100644 index 0000000..5db23d1 --- /dev/null +++ b/docs/migration/deferred-route-modernization-follow-up/observability_checkpoints.md @@ -0,0 +1,44 @@ +# Deferred Route Modernization Observability Checkpoints + +## Route Governance Signals + +1. `navigation_contract_mismatch_total` +- Source: `/api/portal/navigation` diagnostics. +- Alert condition: non-zero for deferred-promoted routes. + +2. `route_contract_missing_metadata_total` +- Source: route governance CI script. +- Alert condition: >0 in block mode for `/tables`, `/excel-query`, `/query-tool`, `/mid-section-defect`. + +## Quality Gate Signals + +1. `quality_gate_failed_total{gate_id}` +- Source: quality gate report. +- Alert condition: any mandatory gate failed for deferred-promoted routes. + +2. `manual_acceptance_pending_routes` +- Source: manual acceptance records. +- Alert condition: cutover attempted with pending sign-off for deferred routes. + +3. `pre_change_confirmation_missing` +- Source: pre-change confirmation records. +- Alert condition: implementation started without recorded confirmation. + +## Fallback and Rollback Signals + +1. `deferred_route_runtime_fallback_served_total` +- Should remain zero after fallback retirement milestone for each route. + +2. `content_cutover_flag_rollbacks_total{route}` +- Track frequency per deferred route. + +3. `legacy_bug_replay_failures_total{route}` +- Any non-zero indicates carry-over risk and blocks sign-off. + +## Cutover Sequence Signals + +1. `deferred_route_cutover_sequence_violation` +- Alert condition: route cutover attempted out of planned sequence. + +2. `deferred_route_signoff_blocked_by_bug_replay` +- Alert condition: sign-off blocked due to reproduced legacy bug. diff --git a/docs/migration/deferred-route-modernization-follow-up/pre_change_confirmation_template.md b/docs/migration/deferred-route-modernization-follow-up/pre_change_confirmation_template.md new file mode 100644 index 0000000..8b1bcf7 --- /dev/null +++ b/docs/migration/deferred-route-modernization-follow-up/pre_change_confirmation_template.md @@ -0,0 +1,35 @@ +# Pre-Change Confirmation Template (Deferred Route) + +## Rule + +Before any implementation work begins on a deferred route, a route-scoped pre-change confirmation MUST be recorded and approved. Implementation is BLOCKED until confirmation exists. + +## Required Fields + +1. **Route**: The deferred route path (e.g., `/tables`). +2. **Status Snapshot**: Current route status in page registry (e.g., `dev`, `released`). +3. **Scope Boundary Check**: Confirmation that the route is listed in `route_scope_matrix.json` as in-scope for this change. +4. **Contract Baseline Refs**: References to existing route contracts and content contracts that define expected behavior. +5. **Known-Bug Baseline Ref**: Reference to `known_bug_baseline.json` entry for this route (or confirmation that baseline is initialized). +6. **Rollback Flag Plan**: Planned feature flag key and rollback strategy for this route's cutover. +7. **Owner**: The person/team responsible for this route's modernization. +8. **Date**: Date of confirmation. +9. **Approved By**: Reviewer who approved the pre-change confirmation. + +## Template + +``` +Route: / +Status Snapshot: +Scope Boundary Check: confirmed in route_scope_matrix.json as in-scope +Contract Baseline Refs: + - Route contract: route_contracts.json#/ + - Content contract: route_content_contracts.json#/ +Known-Bug Baseline Ref: known_bug_baseline.json#/ +Rollback Flag Plan: + - Feature flag: modernization_feature_flags.json#/.content_cutover_enabled + - Rollback strategy: fallback_to_legacy_route +Owner: +Date: +Approved By: +``` diff --git a/docs/migration/deferred-route-modernization-follow-up/pre_change_confirmations.json b/docs/migration/deferred-route-modernization-follow-up/pre_change_confirmations.json new file mode 100644 index 0000000..d2a797a --- /dev/null +++ b/docs/migration/deferred-route-modernization-follow-up/pre_change_confirmations.json @@ -0,0 +1,85 @@ +{ + "change": "deferred-route-modernization-follow-up", + "rule": "implementation is blocked for a route until its pre-change confirmation is recorded and approved", + "required_fields": [ + "route", + "status_snapshot", + "scope_boundary_check", + "contract_baseline_refs", + "known_bug_baseline_ref", + "rollback_flag_plan", + "owner", + "date", + "approved_by" + ], + "records": [ + { + "route": "/tables", + "status_snapshot": "dev", + "scope_boundary_check": "confirmed in route_scope_matrix.json as in-scope (promoted from deferred)", + "contract_baseline_refs": { + "route_contract": "route_contracts.json#/tables", + "content_contract": "route_content_contracts.json#/tables" + }, + "known_bug_baseline_ref": "known_bug_baseline.json#/tables", + "rollback_flag_plan": { + "feature_flag": "modernization_feature_flags.json#/tables.content_cutover_enabled", + "rollback_strategy": "fallback_to_legacy_route" + }, + "owner": "frontend-mes-reporting", + "date": "2026-02-12", + "approved_by": "deferred-route-modernization-follow-up" + }, + { + "route": "/excel-query", + "status_snapshot": "dev", + "scope_boundary_check": "confirmed in route_scope_matrix.json as in-scope (promoted from deferred)", + "contract_baseline_refs": { + "route_contract": "route_contracts.json#/excel-query", + "content_contract": "route_content_contracts.json#/excel-query" + }, + "known_bug_baseline_ref": "known_bug_baseline.json#/excel-query", + "rollback_flag_plan": { + "feature_flag": "modernization_feature_flags.json#/excel-query.content_cutover_enabled", + "rollback_strategy": "fallback_to_legacy_route" + }, + "owner": "frontend-mes-reporting", + "date": "2026-02-12", + "approved_by": "deferred-route-modernization-follow-up" + }, + { + "route": "/query-tool", + "status_snapshot": "dev", + "scope_boundary_check": "confirmed in route_scope_matrix.json as in-scope (promoted from deferred)", + "contract_baseline_refs": { + "route_contract": "route_contracts.json#/query-tool", + "content_contract": "route_content_contracts.json#/query-tool" + }, + "known_bug_baseline_ref": "known_bug_baseline.json#/query-tool", + "rollback_flag_plan": { + "feature_flag": "modernization_feature_flags.json#/query-tool.content_cutover_enabled", + "rollback_strategy": "fallback_to_legacy_route" + }, + "owner": "frontend-mes-reporting", + "date": "2026-02-12", + "approved_by": "deferred-route-modernization-follow-up" + }, + { + "route": "/mid-section-defect", + "status_snapshot": "dev", + "scope_boundary_check": "confirmed in route_scope_matrix.json as in-scope (promoted from deferred)", + "contract_baseline_refs": { + "route_contract": "route_contracts.json#/mid-section-defect", + "content_contract": "route_content_contracts.json#/mid-section-defect" + }, + "known_bug_baseline_ref": "known_bug_baseline.json#/mid-section-defect", + "rollback_flag_plan": { + "feature_flag": "modernization_feature_flags.json#/mid-section-defect.content_cutover_enabled", + "rollback_strategy": "fallback_to_legacy_route" + }, + "owner": "frontend-mes-reporting", + "date": "2026-02-12", + "approved_by": "deferred-route-modernization-follow-up" + } + ] +} diff --git a/docs/migration/deferred-route-modernization-follow-up/rollback_controls.md b/docs/migration/deferred-route-modernization-follow-up/rollback_controls.md new file mode 100644 index 0000000..1be9ad3 --- /dev/null +++ b/docs/migration/deferred-route-modernization-follow-up/rollback_controls.md @@ -0,0 +1,31 @@ +# Deferred Route Modernization Rollback Controls + +## Route-Level Reversion Controls + +- **Content cutover feature flag**: Set `content_cutover_enabled: false` in `data/modernization_feature_flags.json` for the affected route to immediately revert to legacy content path. +- **PORTAL_SPA_ENABLED=false**: Disable shell-first navigation runtime globally (affects all routes). +- **Route-scoped contract fallback**: Mark route contract with fallback strategy and redeploy shell assets. + +## Per-Route Rollback Procedure + +1. Set `content_cutover_enabled: false` for the affected route in `modernization_feature_flags.json`. +2. Restart workers to pick up the flag change. +3. Verify legacy content path is serving correctly. +4. Record rollback in manual acceptance records with reason and timestamp. +5. Investigate root cause before re-enabling cutover. + +## False-Positive Gate Handling + +1. Capture failing gate output and route impact. +2. Confirm whether failure is test flake or product defect. +3. If false-positive and production risk is high: +- Temporarily switch gate severity from `block` to `warn`. +- Record waiver with owner, reason, expiry. +4. Restore `block` mode after corrective action. + +## Required Rollback Evidence + +- Incident timestamp and impacted route. +- Gate IDs that triggered rollback. +- Feature flag state before/after rollback. +- Manual acceptance and known-bug replay references. diff --git a/docs/migration/deferred-route-modernization-follow-up/rollout_runbook.md b/docs/migration/deferred-route-modernization-follow-up/rollout_runbook.md new file mode 100644 index 0000000..3f71b96 --- /dev/null +++ b/docs/migration/deferred-route-modernization-follow-up/rollout_runbook.md @@ -0,0 +1,47 @@ +# Deferred Route Modernization Rollout Runbook + +## Upstream Reference + +- Phase 1 runbook: `docs/migration/full-modernization-architecture-blueprint/rollout_runbook.md` + +## Phase Sequence + +1. Governance freeze +- Confirm `route_scope_matrix.json` has deferred routes promoted to in-scope. +- Confirm pre-change confirmations recorded for all 4 routes. +- Confirm exception registry has no unresolved blocking entries. + +2. Route governance enforcement +- Run route contract completeness checks in warn mode. +- Fix all deferred-route metadata gaps. +- Promote route governance checks to block mode. + +3. Per-route content modernization (sequential) +- Enable content cutover flag for first route (`/tables`). +- Execute parity checks and manual acceptance. +- Run known-bug replay checks. +- On pass: sign off and proceed to next route. +- On fail: rollback flag and investigate. + +4. Cutover sequence +- `/tables` -> `/excel-query` -> `/query-tool` -> `/mid-section-defect` +- Next route blocked until current route has approved sign-off. + +5. Asset/gate enforcement +- Validate deferred route asset readiness. +- Run quality gate suite (functional, visual, accessibility, performance). +- Promote gate severity from warn to block per milestones. + +6. Fallback retirement +- Retire runtime fallback for deferred routes after all acceptance gates pass. + +## Hold Points + +- Hold-1: Any deferred route missing contract metadata or pre-change confirmation. +- Hold-2: Any parity failure or known-bug replay failure. +- Hold-3: Any mandatory quality gate failure in block mode. +- Hold-4: Cutover attempted before previous route sign-off complete. + +## Promotion Rule + +Promotion is allowed only when all hold points are clear for the current route in sequence. diff --git a/docs/migration/deferred-route-modernization-follow-up/route_scope_matrix.json b/docs/migration/deferred-route-modernization-follow-up/route_scope_matrix.json new file mode 100644 index 0000000..dfb5837 --- /dev/null +++ b/docs/migration/deferred-route-modernization-follow-up/route_scope_matrix.json @@ -0,0 +1,100 @@ +{ + "change": "deferred-route-modernization-follow-up", + "upstream_change": "full-modernization-architecture-blueprint", + "generated_at": "2026-02-12T00:00:00Z", + "phase": "phase-2-deferred-route-modernization", + "policy": { + "scope_is_frozen": true, + "out_of_scope_tasks_must_be_rejected": true, + "dev_routes_are_eligible": true, + "released_only_restriction_does_not_apply": true + }, + "in_scope": [ + { + "route": "/tables", + "category": "report", + "canonical_shell_path": "/portal-shell/tables", + "owner": "frontend-mes-reporting", + "visibility_policy": "released_or_admin", + "prior_scope": "deferred", + "prior_change": "full-modernization-architecture-blueprint" + }, + { + "route": "/excel-query", + "category": "report", + "canonical_shell_path": "/portal-shell/excel-query", + "owner": "frontend-mes-reporting", + "visibility_policy": "released_or_admin", + "prior_scope": "deferred", + "prior_change": "full-modernization-architecture-blueprint" + }, + { + "route": "/query-tool", + "category": "report", + "canonical_shell_path": "/portal-shell/query-tool", + "owner": "frontend-mes-reporting", + "visibility_policy": "released_or_admin", + "prior_scope": "deferred", + "prior_change": "full-modernization-architecture-blueprint" + }, + { + "route": "/mid-section-defect", + "category": "report", + "canonical_shell_path": "/portal-shell/mid-section-defect", + "owner": "frontend-mes-reporting", + "visibility_policy": "released_or_admin", + "prior_scope": "deferred", + "prior_change": "full-modernization-architecture-blueprint" + } + ], + "out_of_scope": [ + { + "route": "/wip-overview", + "reason": "completed-in-phase-1" + }, + { + "route": "/wip-detail", + "reason": "completed-in-phase-1" + }, + { + "route": "/hold-overview", + "reason": "completed-in-phase-1" + }, + { + "route": "/hold-detail", + "reason": "completed-in-phase-1" + }, + { + "route": "/hold-history", + "reason": "completed-in-phase-1" + }, + { + "route": "/resource", + "reason": "completed-in-phase-1" + }, + { + "route": "/resource-history", + "reason": "completed-in-phase-1" + }, + { + "route": "/qc-gate", + "reason": "completed-in-phase-1" + }, + { + "route": "/job-query", + "reason": "completed-in-phase-1" + }, + { + "route": "/tmtt-defect", + "reason": "completed-in-phase-1" + }, + { + "route": "/admin/pages", + "reason": "completed-in-phase-1" + }, + { + "route": "/admin/performance", + "reason": "completed-in-phase-1" + } + ] +} diff --git a/docs/migration/deferred-route-modernization-follow-up/scope_boundary_note.md b/docs/migration/deferred-route-modernization-follow-up/scope_boundary_note.md new file mode 100644 index 0000000..4d69015 --- /dev/null +++ b/docs/migration/deferred-route-modernization-follow-up/scope_boundary_note.md @@ -0,0 +1,30 @@ +# Scope Boundary Note + +## Change + +`deferred-route-modernization-follow-up` + +## Scope Clarification + +Deferred routes (`/tables`, `/excel-query`, `/query-tool`, `/mid-section-defect`) are **in-scope** for this follow-up change regardless of their current page status (`dev` or `released`). + +The **released-only restriction does not apply** to this change. These routes were intentionally deferred from phase 1 to control blast radius and are now the explicit modernization target. + +## What Is In-Scope + +- `/tables` — currently `dev` in page registry +- `/excel-query` — currently `dev` in page registry +- `/query-tool` — currently `dev` in page registry +- `/mid-section-defect` — currently `dev` in page registry + +## What Is NOT In-Scope + +- Phase-1 routes that are already modernized and governed (10 report + 2 admin routes) +- Routes outside the deferred matrix +- Backend business data semantics beyond compatibility safeguards +- Unrelated admin/report features not in the deferred matrix + +## Policy + +- Phase-1 in-scope routes SHALL NOT be reopened by this change unless explicitly required for shared governance wiring. +- Deferred routes adopt identical governance rigor (contracts, parity, manual acceptance, bug replay) as phase-1 routes. diff --git a/docs/migration/deferred-route-modernization-follow-up/upstream_linkage.json b/docs/migration/deferred-route-modernization-follow-up/upstream_linkage.json new file mode 100644 index 0000000..f826939 --- /dev/null +++ b/docs/migration/deferred-route-modernization-follow-up/upstream_linkage.json @@ -0,0 +1,27 @@ +{ + "change": "deferred-route-modernization-follow-up", + "upstream_change": "full-modernization-architecture-blueprint", + "handoff_artifact": "docs/migration/full-modernization-architecture-blueprint/deferred_route_handoff.md", + "consumed_artifacts": { + "scope_boundary": "docs/migration/full-modernization-architecture-blueprint/route_scope_matrix.json", + "parity_fixtures": "docs/migration/full-modernization-architecture-blueprint/parity_golden_fixtures.json", + "interaction_parity_checks": "docs/migration/full-modernization-architecture-blueprint/interaction_parity_checks.json", + "manual_acceptance_checklist": "docs/migration/full-modernization-architecture-blueprint/page_content_manual_acceptance_checklist.md", + "known_bug_baseline": "docs/migration/full-modernization-architecture-blueprint/known_bug_baseline.json", + "bug_revalidation_records": "docs/migration/full-modernization-architecture-blueprint/bug_revalidation_records.json", + "quality_gate_policy": "docs/migration/full-modernization-architecture-blueprint/quality_gate_policy.json", + "governance_milestones": "docs/migration/full-modernization-architecture-blueprint/governance_milestones.md", + "asset_readiness_manifest": "docs/migration/full-modernization-architecture-blueprint/asset_readiness_manifest.json" + }, + "transfer_rules": [ + "Deferred routes remain excluded from phase-1 blocking criteria", + "Follow-up change MUST promote these routes to in-scope before legacy retirement", + "Equivalent parity/manual-acceptance/bug-revalidation gates must be applied" + ], + "deferred_routes": [ + "/tables", + "/excel-query", + "/query-tool", + "/mid-section-defect" + ] +} diff --git a/docs/migration/full-modernization-architecture-blueprint/asset_readiness_manifest.json b/docs/migration/full-modernization-architecture-blueprint/asset_readiness_manifest.json index ec0e806..922cc2e 100644 --- a/docs/migration/full-modernization-architecture-blueprint/asset_readiness_manifest.json +++ b/docs/migration/full-modernization-architecture-blueprint/asset_readiness_manifest.json @@ -11,12 +11,11 @@ "/resource-history": ["resource-history.html", "resource-history.js"], "/qc-gate": ["qc-gate.html", "qc-gate.js"], "/job-query": ["job-query.js"], - "/tmtt-defect": ["tmtt-defect.js"] + "/tmtt-defect": ["tmtt-defect.js"], + "/tables": ["tables.html", "tables.js"], + "/excel-query": ["excel-query.js"], + "/query-tool": ["query-tool.js"], + "/mid-section-defect": ["mid-section-defect.html", "mid-section-defect.js"] }, - "deferred_routes": [ - "/tables", - "/excel-query", - "/query-tool", - "/mid-section-defect" - ] + "deferred_routes": [] } diff --git a/docs/migration/full-modernization-architecture-blueprint/canonical_routing_policy.json b/docs/migration/full-modernization-architecture-blueprint/canonical_routing_policy.json index 99a29d2..ae05649 100644 --- a/docs/migration/full-modernization-architecture-blueprint/canonical_routing_policy.json +++ b/docs/migration/full-modernization-architecture-blueprint/canonical_routing_policy.json @@ -19,6 +19,10 @@ "/resource-history", "/qc-gate", "/job-query", - "/tmtt-defect" + "/tmtt-defect", + "/tables", + "/excel-query", + "/query-tool", + "/mid-section-defect" ] } diff --git a/docs/migration/full-modernization-architecture-blueprint/exception_registry.json b/docs/migration/full-modernization-architecture-blueprint/exception_registry.json index 2b0fd71..c010cd0 100644 --- a/docs/migration/full-modernization-architecture-blueprint/exception_registry.json +++ b/docs/migration/full-modernization-architecture-blueprint/exception_registry.json @@ -37,6 +37,30 @@ "status": "approved-temporary", "milestone": "2026-03-19", "tracking_issue": "deferred-route-modernization-follow-up/admin-template-modernization" + }, + { + "id": "style-excel-query-shell-tokens-no-fallback", + "type": "style", + "scope": "/excel-query", + "owner": "frontend-mes-reporting", + "introduced_by": "legacy-template", + "reason": "shell tokens without CSS fallback values; inherited from legacy era", + "mitigation": "add fallback values during content modernization cutover", + "status": "approved-temporary", + "milestone": "2026-03-19", + "tracking_issue": "deferred-route-modernization-follow-up/excel-query-style-hardening" + }, + { + "id": "style-query-tool-shell-tokens-no-fallback", + "type": "style", + "scope": "/query-tool", + "owner": "frontend-mes-reporting", + "introduced_by": "legacy-template", + "reason": "shell tokens without CSS fallback values; inherited from legacy era", + "mitigation": "add fallback values during content modernization cutover", + "status": "approved-temporary", + "milestone": "2026-03-19", + "tracking_issue": "deferred-route-modernization-follow-up/query-tool-style-hardening" } ] } diff --git a/docs/migration/full-modernization-architecture-blueprint/interaction_parity_checks.json b/docs/migration/full-modernization-architecture-blueprint/interaction_parity_checks.json index dc7a5ee..1ed4151 100644 --- a/docs/migration/full-modernization-architecture-blueprint/interaction_parity_checks.json +++ b/docs/migration/full-modernization-architecture-blueprint/interaction_parity_checks.json @@ -12,6 +12,10 @@ "/job-query": ["resource_select", "query_submit", "export_csv"], "/tmtt-defect": ["date_range_query", "chart_filter_link", "detail_sort"], "/admin/pages": ["drawer_crud", "page_status_update", "admin_visibility"], - "/admin/performance": ["admin_auth_access", "performance_view_load"] + "/admin/performance": ["admin_auth_access", "performance_view_load"], + "/tables": ["table_select", "column_filter_apply", "query_submit"], + "/excel-query": ["file_upload", "column_select", "query_execute", "export_csv"], + "/query-tool": ["lot_resolve", "history_load", "association_query", "equipment_period_query", "export_csv"], + "/mid-section-defect": ["date_range_query", "loss_reason_filter", "pareto_chart_drill", "detail_pagination", "export_csv"] } } diff --git a/docs/migration/full-modernization-architecture-blueprint/known_bug_baseline.json b/docs/migration/full-modernization-architecture-blueprint/known_bug_baseline.json index 220c0fa..7788dfe 100644 --- a/docs/migration/full-modernization-architecture-blueprint/known_bug_baseline.json +++ b/docs/migration/full-modernization-architecture-blueprint/known_bug_baseline.json @@ -1,6 +1,6 @@ { "change": "full-modernization-architecture-blueprint", - "scope": "in-scope-routes-only", + "scope": "all-governed-routes", "routes": { "/wip-overview": { "baseline_status": "initialized", @@ -49,6 +49,22 @@ "/admin/performance": { "baseline_status": "initialized", "known_bugs": [] + }, + "/tables": { + "baseline_status": "initialized", + "known_bugs": [] + }, + "/excel-query": { + "baseline_status": "initialized", + "known_bugs": [] + }, + "/query-tool": { + "baseline_status": "initialized", + "known_bugs": [] + }, + "/mid-section-defect": { + "baseline_status": "initialized", + "known_bugs": [] } }, "revalidation_rule": { diff --git a/docs/migration/full-modernization-architecture-blueprint/parity_golden_fixtures.json b/docs/migration/full-modernization-architecture-blueprint/parity_golden_fixtures.json index fa5a438..670f289 100644 --- a/docs/migration/full-modernization-architecture-blueprint/parity_golden_fixtures.json +++ b/docs/migration/full-modernization-architecture-blueprint/parity_golden_fixtures.json @@ -12,6 +12,10 @@ "/job-query": ["docs/migration/portal-shell-route-view-integration/wave-b-parity-evidence.json"], "/tmtt-defect": ["docs/migration/portal-shell-route-view-integration/wave-b-parity-evidence.json"], "/admin/pages": ["tests/test_portal_shell_routes.py"], - "/admin/performance": ["tests/test_performance_integration.py"] + "/admin/performance": ["tests/test_performance_integration.py"], + "/tables": ["tests/test_portal_shell_routes.py"], + "/excel-query": ["tests/test_portal_shell_routes.py"], + "/query-tool": ["tests/test_portal_shell_routes.py"], + "/mid-section-defect": ["tests/test_portal_shell_routes.py"] } } diff --git a/docs/migration/full-modernization-architecture-blueprint/quality_gate_policy.json b/docs/migration/full-modernization-architecture-blueprint/quality_gate_policy.json index 67ccf00..49338bc 100644 --- a/docs/migration/full-modernization-architecture-blueprint/quality_gate_policy.json +++ b/docs/migration/full-modernization-architecture-blueprint/quality_gate_policy.json @@ -12,10 +12,5 @@ {"id": "Q4", "name": "performance-budget", "required": true}, {"id": "Q5", "name": "manual-acceptance-and-bug-revalidation", "required": true} ], - "deferred_routes_excluded": [ - "/tables", - "/excel-query", - "/query-tool", - "/mid-section-defect" - ] + "deferred_routes_excluded": [] } diff --git a/docs/migration/full-modernization-architecture-blueprint/quality_gate_report.json b/docs/migration/full-modernization-architecture-blueprint/quality_gate_report.json index 9c97008..52fd667 100644 --- a/docs/migration/full-modernization-architecture-blueprint/quality_gate_report.json +++ b/docs/migration/full-modernization-architecture-blueprint/quality_gate_report.json @@ -1,7 +1,10 @@ { "mode": "block", "errors": [], - "warnings": [], + "warnings": [ + "/excel-query uses shell tokens without fallback ['--portal-brand-end', '--portal-brand-start', '--portal-shadow-panel'] in frontend/src/excel-query/style.css with approved exception", + "/query-tool uses shell tokens without fallback ['--portal-brand-end', '--portal-brand-start', '--portal-shadow-panel'] in frontend/src/query-tool/style.css with approved exception" + ], "info": [], "passed": true } diff --git a/docs/migration/full-modernization-architecture-blueprint/route_content_contracts.json b/docs/migration/full-modernization-architecture-blueprint/route_content_contracts.json index 7a13411..abb3dee 100644 --- a/docs/migration/full-modernization-architecture-blueprint/route_content_contracts.json +++ b/docs/migration/full-modernization-architecture-blueprint/route_content_contracts.json @@ -72,6 +72,30 @@ "query_payload_contract": ["performance_summary", "log_stream"], "chart_data_shape": ["timeline", "status_summary"], "state_contract": ["loading", "empty", "error", "success"] + }, + "/tables": { + "filter_input_semantics": ["table_category", "table_name", "dynamic_column_filters"], + "query_payload_contract": ["table_name", "limit", "time_field", "filters"], + "chart_data_shape": ["dynamic_data_table"], + "state_contract": ["loading", "empty", "error", "success"] + }, + "/excel-query": { + "filter_input_semantics": ["file_upload", "excel_column", "table_name", "search_column", "query_type", "return_columns", "date_range"], + "query_payload_contract": ["table_name", "search_column", "return_columns", "search_values", "query_type", "date_column", "date_from", "date_to"], + "chart_data_shape": ["result_table", "csv_export"], + "state_contract": ["loading", "empty", "error", "success"] + }, + "/query-tool": { + "filter_input_semantics": ["input_type", "workcenter_groups", "input_values", "association_type", "equipment_ids", "equipment_query_type", "start_date", "end_date"], + "query_payload_contract": ["input_type", "values", "container_id", "workcenter_groups", "type", "equipment_ids", "equipment_names", "start_date", "end_date", "query_type"], + "chart_data_shape": ["resolved_lots_table", "lot_history_table", "association_table", "equipment_table"], + "state_contract": ["loading", "empty", "error", "success"] + }, + "/mid-section-defect": { + "filter_input_semantics": ["start_date", "end_date", "loss_reasons"], + "query_payload_contract": ["start_date", "end_date", "loss_reasons", "page", "page_size"], + "chart_data_shape": ["kpi_cards", "pareto_by_station", "pareto_by_loss_reason", "pareto_by_machine", "pareto_by_tmtt_machine", "pareto_by_workflow", "pareto_by_package", "daily_trend", "detail_table"], + "state_contract": ["loading", "empty", "error", "success"] } } } diff --git a/docs/migration/full-modernization-architecture-blueprint/route_contracts.json b/docs/migration/full-modernization-architecture-blueprint/route_contracts.json index b78d187..b53916f 100644 --- a/docs/migration/full-modernization-architecture-blueprint/route_contracts.json +++ b/docs/migration/full-modernization-architecture-blueprint/route_contracts.json @@ -125,7 +125,7 @@ { "route": "/tables", "route_id": "tables", - "scope": "deferred", + "scope": "in-scope", "render_mode": "native", "owner": "frontend-mes-reporting", "visibility_policy": "released_or_admin", @@ -135,7 +135,7 @@ { "route": "/excel-query", "route_id": "excel-query", - "scope": "deferred", + "scope": "in-scope", "render_mode": "native", "owner": "frontend-mes-reporting", "visibility_policy": "released_or_admin", @@ -145,7 +145,7 @@ { "route": "/query-tool", "route_id": "query-tool", - "scope": "deferred", + "scope": "in-scope", "render_mode": "native", "owner": "frontend-mes-reporting", "visibility_policy": "released_or_admin", @@ -155,7 +155,7 @@ { "route": "/mid-section-defect", "route_id": "mid-section-defect", - "scope": "deferred", + "scope": "in-scope", "render_mode": "native", "owner": "frontend-mes-reporting", "visibility_policy": "released_or_admin", diff --git a/docs/migration/full-modernization-architecture-blueprint/route_scope_matrix.json b/docs/migration/full-modernization-architecture-blueprint/route_scope_matrix.json index 3b483de..199cae7 100644 --- a/docs/migration/full-modernization-architecture-blueprint/route_scope_matrix.json +++ b/docs/migration/full-modernization-architecture-blueprint/route_scope_matrix.json @@ -90,24 +90,43 @@ "canonical_shell_path": "/portal-shell/admin/performance", "owner": "frontend-platform-admin", "visibility_policy": "admin_only" - } - ], - "deferred": [ + }, { "route": "/tables", - "reason": "deferred-to-follow-up-change" + "category": "report", + "canonical_shell_path": "/portal-shell/tables", + "owner": "frontend-mes-reporting", + "visibility_policy": "released_or_admin", + "promoted_from": "deferred", + "promoted_by": "deferred-route-modernization-follow-up" }, { "route": "/excel-query", - "reason": "deferred-to-follow-up-change" + "category": "report", + "canonical_shell_path": "/portal-shell/excel-query", + "owner": "frontend-mes-reporting", + "visibility_policy": "released_or_admin", + "promoted_from": "deferred", + "promoted_by": "deferred-route-modernization-follow-up" }, { "route": "/query-tool", - "reason": "deferred-to-follow-up-change" + "category": "report", + "canonical_shell_path": "/portal-shell/query-tool", + "owner": "frontend-mes-reporting", + "visibility_policy": "released_or_admin", + "promoted_from": "deferred", + "promoted_by": "deferred-route-modernization-follow-up" }, { "route": "/mid-section-defect", - "reason": "deferred-to-follow-up-change" + "category": "report", + "canonical_shell_path": "/portal-shell/mid-section-defect", + "owner": "frontend-mes-reporting", + "visibility_policy": "released_or_admin", + "promoted_from": "deferred", + "promoted_by": "deferred-route-modernization-follow-up" } - ] + ], + "deferred": [] } diff --git a/docs/migration/full-modernization-architecture-blueprint/style_inventory.json b/docs/migration/full-modernization-architecture-blueprint/style_inventory.json index 0817b78..ae9bc1e 100644 --- a/docs/migration/full-modernization-architecture-blueprint/style_inventory.json +++ b/docs/migration/full-modernization-architecture-blueprint/style_inventory.json @@ -21,7 +21,11 @@ "global_selectors": ["body"], "status": "exception-registered", "exception_id": "style-admin-performance-inline-css" - } + }, + "/tables": {"global_selectors": [], "status": "pending-audit"}, + "/excel-query": {"global_selectors": [], "status": "pending-audit"}, + "/query-tool": {"global_selectors": [], "status": "pending-audit"}, + "/mid-section-defect": {"global_selectors": [], "status": "pending-audit"} }, "shared_layers": { "frontend/src/styles/tailwind.css": [":root", "body"], 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 index 8cd6abe..c4f2230 100644 --- a/docs/migration/portal-shell-route-view-integration/visual-regression-snapshots.json +++ b/docs/migration/portal-shell-route-view-integration/visual-regression-snapshots.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-02-11T17:49:00+08:00", + "generated_at": "2026-02-12T12:40:00+08:00", "description": "Critical visual-state snapshots for chart/table/matrix routes.", "critical_diff_policy": { "block_release": true, @@ -27,7 +27,7 @@ "frontend/src/hold-overview/components/HoldMatrix.vue", "frontend/src/hold-overview/style.css" ], - "fingerprint": "5d42352bfb3de23e2ea5638285b69e2fc8adf6f69d61989f0280739b58fedf4d" + "fingerprint": "f2ca1666f50afb4f922b522cdf739685ce068911a17d6e6c285244770f451f2c" }, { "id": "qc-gate-chart-table-linked", @@ -39,7 +39,7 @@ "frontend/src/qc-gate/components/QcGateChart.vue", "frontend/src/qc-gate/style.css" ], - "fingerprint": "2d283febab9142f042a7961aef93201a9d75f43c248cdd40b6b4530101b29619" + "fingerprint": "13e000938f5fc398a9abf2c62b3e64dd0c4742ba87b20d144c18befd57e2e1f4" }, { "id": "resource-history-chart-detail", @@ -53,7 +53,7 @@ "frontend/src/resource-history/components/DetailSection.vue", "frontend/src/resource-history/style.css" ], - "fingerprint": "ec5560c3fd233de9d3a31928965e2c71c2e878cb203076e4b45ef149c46a5387" + "fingerprint": "385910e89f10f016f7973e97be30a697a396a71ea470c1bcfe028a2c6daa4cc9" }, { "id": "tmtt-defect-pareto-detail", @@ -66,7 +66,7 @@ "frontend/src/tmtt-defect/components/TmttKpiCards.vue", "frontend/src/tmtt-defect/style.css" ], - "fingerprint": "59059868a9f61a20160d2acc8602ee9aa1a494ec0fdb6a816ee98028517451e8" + "fingerprint": "141d712008d33887a103a5a5133543d527be208c2a5700d16f4c045ce13bb166" } ] } diff --git a/frontend/src/excel-query/App.vue b/frontend/src/excel-query/App.vue index 05bb426..71b5fa5 100644 --- a/frontend/src/excel-query/App.vue +++ b/frontend/src/excel-query/App.vue @@ -57,7 +57,6 @@ onMounted(async () => {

Excel 批次查詢

-

Native Route-View:Upload / Detect / Query / Export

diff --git a/frontend/src/excel-query/style.css b/frontend/src/excel-query/style.css index 0ee048d..f3639eb 100644 --- a/frontend/src/excel-query/style.css +++ b/frontend/src/excel-query/style.css @@ -9,7 +9,7 @@ padding: 22px 24px; margin-bottom: 16px; color: #fff; - background: linear-gradient(135deg, var(--portal-brand-start) 0%, var(--portal-brand-end) 100%); + background: linear-gradient(135deg, var(--portal-brand-start, #667eea) 0%, var(--portal-brand-end, #764ba2) 100%); box-shadow: var(--portal-shadow-panel); } diff --git a/frontend/src/hold-detail/style.css b/frontend/src/hold-detail/style.css index 5870d77..a989118 100644 --- a/frontend/src/hold-detail/style.css +++ b/frontend/src/hold-detail/style.css @@ -1,7 +1,7 @@ @import '../wip-shared/styles.css'; .hold-detail-page .header h1 { - font-size: 20px; + font-size: 24px; } .hold-type-badge { diff --git a/frontend/src/hold-history/style.css b/frontend/src/hold-history/style.css index 4e5a729..07e99dd 100644 --- a/frontend/src/hold-history/style.css +++ b/frontend/src/hold-history/style.css @@ -1,9 +1,9 @@ .hold-history-header { - background: linear-gradient(135deg, #0f766e 0%, #0ea5e9 100%); + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } .hold-history-page .header h1 { - font-size: 22px; + font-size: 24px; } .hold-type-badge { diff --git a/frontend/src/hold-overview/style.css b/frontend/src/hold-overview/style.css index e50af2e..4e573dd 100644 --- a/frontend/src/hold-overview/style.css +++ b/frontend/src/hold-overview/style.css @@ -1,5 +1,5 @@ .hold-overview-page .header h1 { - font-size: 22px; + font-size: 24px; } .hold-type-badge { diff --git a/frontend/src/job-query/App.vue b/frontend/src/job-query/App.vue index ad1a15d..5cef0c5 100644 --- a/frontend/src/job-query/App.vue +++ b/frontend/src/job-query/App.vue @@ -61,7 +61,6 @@ onMounted(async () => {

設備維修查詢

-

Native Route-View:查詢 / 交易歷程 / CSV 匯出

diff --git a/frontend/src/job-query/style.css b/frontend/src/job-query/style.css index be5535e..dac111d 100644 --- a/frontend/src/job-query/style.css +++ b/frontend/src/job-query/style.css @@ -11,8 +11,8 @@ color: #fff; background: linear-gradient( 135deg, - var(--portal-brand-start, #6366f1) 0%, - var(--portal-brand-end, #7c3aed) 100% + var(--portal-brand-start, #667eea) 0%, + var(--portal-brand-end, #764ba2) 100% ); box-shadow: var(--portal-shadow-panel, 0 8px 24px rgba(79, 70, 229, 0.18)); } diff --git a/frontend/src/mid-section-defect/style.css b/frontend/src/mid-section-defect/style.css index b1bbff0..1bfec10 100644 --- a/frontend/src/mid-section-defect/style.css +++ b/frontend/src/mid-section-defect/style.css @@ -36,7 +36,7 @@ body { padding: 16px 20px; border-radius: 12px; margin-bottom: 16px; - background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a855f7 100%); + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); box-shadow: var(--msd-shadow-md); } diff --git a/frontend/src/portal-shell/routeContracts.js b/frontend/src/portal-shell/routeContracts.js index 722de6b..21a54ab 100644 --- a/frontend/src/portal-shell/routeContracts.js +++ b/frontend/src/portal-shell/routeContracts.js @@ -9,6 +9,10 @@ const IN_SCOPE_REPORT_ROUTES = Object.freeze([ '/qc-gate', '/job-query', '/tmtt-defect', + '/tables', + '/excel-query', + '/query-tool', + '/mid-section-defect', ]); const IN_SCOPE_ADMIN_ROUTES = Object.freeze([ @@ -16,12 +20,7 @@ const IN_SCOPE_ADMIN_ROUTES = Object.freeze([ '/admin/performance', ]); -const DEFERRED_ROUTES = Object.freeze([ - '/tables', - '/excel-query', - '/query-tool', - '/mid-section-defect', -]); +const DEFERRED_ROUTES = Object.freeze([]); const ALL_KNOWN_ROUTES = Object.freeze([ ...IN_SCOPE_REPORT_ROUTES, @@ -195,8 +194,8 @@ const ROUTE_CONTRACTS = Object.freeze({ title: '表格總覽', rollbackStrategy: 'fallback_to_legacy_route', visibilityPolicy: 'released_or_admin', - scope: 'deferred', - compatibilityPolicy: 'legacy_direct_entry_allowed', + scope: 'in-scope', + compatibilityPolicy: 'redirect_to_shell_when_spa_enabled', }), '/excel-query': buildContract({ route: '/excel-query', @@ -206,8 +205,8 @@ const ROUTE_CONTRACTS = Object.freeze({ title: 'Excel 查詢工具', rollbackStrategy: 'fallback_to_legacy_route', visibilityPolicy: 'released_or_admin', - scope: 'deferred', - compatibilityPolicy: 'legacy_direct_entry_allowed', + scope: 'in-scope', + compatibilityPolicy: 'redirect_to_shell_when_spa_enabled', }), '/query-tool': buildContract({ route: '/query-tool', @@ -217,8 +216,8 @@ const ROUTE_CONTRACTS = Object.freeze({ title: 'Query Tool', rollbackStrategy: 'fallback_to_legacy_route', visibilityPolicy: 'released_or_admin', - scope: 'deferred', - compatibilityPolicy: 'legacy_direct_entry_allowed', + scope: 'in-scope', + compatibilityPolicy: 'redirect_to_shell_when_spa_enabled', }), '/mid-section-defect': buildContract({ route: '/mid-section-defect', @@ -228,8 +227,8 @@ const ROUTE_CONTRACTS = Object.freeze({ title: '中段製程不良追溯', rollbackStrategy: 'fallback_to_legacy_route', visibilityPolicy: 'released_or_admin', - scope: 'deferred', - compatibilityPolicy: 'legacy_direct_entry_allowed', + scope: 'in-scope', + compatibilityPolicy: 'redirect_to_shell_when_spa_enabled', }), }); diff --git a/frontend/src/qc-gate/style.css b/frontend/src/qc-gate/style.css index 8625d7f..1a527c7 100644 --- a/frontend/src/qc-gate/style.css +++ b/frontend/src/qc-gate/style.css @@ -9,8 +9,8 @@ --text: #1f2937; --muted: #64748b; --border: #dbe3ef; - --header-from: var(--portal-brand-start, #6366f1); - --header-to: var(--portal-brand-end, #7c3aed); + --header-from: var(--portal-brand-start, #667eea); + --header-to: var(--portal-brand-end, #764ba2); --success: #22c55e; --warning: #facc15; --danger: #ef4444; @@ -40,7 +40,7 @@ .qc-gate-header h1 { margin: 0; - font-size: 28px; + font-size: 24px; line-height: 1.2; } diff --git a/frontend/src/query-tool/App.vue b/frontend/src/query-tool/App.vue index 5473e93..6b7e7a0 100644 --- a/frontend/src/query-tool/App.vue +++ b/frontend/src/query-tool/App.vue @@ -60,7 +60,6 @@ onMounted(async () => {

批次追蹤工具

-

Native Route-View:Resolve / History / Association / Equipment Period

diff --git a/frontend/src/query-tool/style.css b/frontend/src/query-tool/style.css index 1d165a5..99ee5b9 100644 --- a/frontend/src/query-tool/style.css +++ b/frontend/src/query-tool/style.css @@ -9,7 +9,7 @@ padding: 22px 24px; margin-bottom: 16px; color: #fff; - background: linear-gradient(135deg, var(--portal-brand-start) 0%, var(--portal-brand-end) 100%); + background: linear-gradient(135deg, var(--portal-brand-start, #667eea) 0%, var(--portal-brand-end, #764ba2) 100%); box-shadow: var(--portal-shadow-panel); } diff --git a/frontend/src/resource-history/style.css b/frontend/src/resource-history/style.css index 05df7d3..0d1a926 100644 --- a/frontend/src/resource-history/style.css +++ b/frontend/src/resource-history/style.css @@ -1,5 +1,5 @@ .history-header { - background: linear-gradient(135deg, #4f46e5 0%, #0ea5e9 100%); + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } .filter-row { diff --git a/frontend/src/resource-shared/styles.css b/frontend/src/resource-shared/styles.css index 0c8c19d..436a550 100644 --- a/frontend/src/resource-shared/styles.css +++ b/frontend/src/resource-shared/styles.css @@ -48,7 +48,7 @@ body { padding: 16px 20px; border-radius: 12px; margin-bottom: 16px; - background: linear-gradient(135deg, #2563eb 0%, #7c3aed 100%); + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); box-shadow: var(--resource-shadow-md); } diff --git a/frontend/src/tables/style.css b/frontend/src/tables/style.css index 9c5375c..7bbba16 100644 --- a/frontend/src/tables/style.css +++ b/frontend/src/tables/style.css @@ -47,7 +47,7 @@ body { } .header { - background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%); + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: #ffffff; padding: 24px 28px; display: flex; @@ -58,7 +58,7 @@ body { .header h1 { margin: 0 0 8px; - font-size: 28px; + font-size: 24px; } .header p { diff --git a/frontend/src/tmtt-defect/style.css b/frontend/src/tmtt-defect/style.css index 1a5a7f0..354251b 100644 --- a/frontend/src/tmtt-defect/style.css +++ b/frontend/src/tmtt-defect/style.css @@ -11,8 +11,8 @@ color: #fff; background: linear-gradient( 135deg, - var(--portal-brand-start, #6366f1) 0%, - var(--portal-brand-end, #7c3aed) 100% + var(--portal-brand-start, #667eea) 0%, + var(--portal-brand-end, #764ba2) 100% ); box-shadow: var(--portal-shadow-panel, 0 8px 24px rgba(79, 70, 229, 0.18)); } diff --git a/frontend/tests/portal-shell-route-contract-governance.test.js b/frontend/tests/portal-shell-route-contract-governance.test.js index 944a45a..3b330cd 100644 --- a/frontend/tests/portal-shell-route-contract-governance.test.js +++ b/frontend/tests/portal-shell-route-contract-governance.test.js @@ -19,8 +19,8 @@ const backendContractPath = path.resolve( ); -test('in-scope route contracts satisfy governance metadata requirements', () => { - const errors = validateRouteContractMap({ inScopeOnly: true }); +test('all route contracts satisfy governance metadata requirements', () => { + const errors = validateRouteContractMap(); assert.deepEqual(errors, []); }); @@ -38,14 +38,18 @@ test('admin shell targets are governed and rendered as external targets', () => }); -test('deferred routes stay discoverable but are separable from in-scope gates', () => { +test('formerly deferred routes are now promoted to in-scope', () => { const inScope = new Set(getInScopeRoutes()); const deferred = getDeferredRoutes(); + const promotedRoutes = ['/tables', '/excel-query', '/query-tool', '/mid-section-defect']; - deferred.forEach((route) => { - assert.equal(inScope.has(route), false, `deferred route leaked into in-scope: ${route}`); + assert.equal(deferred.length, 0, 'all deferred routes should be promoted'); + + promotedRoutes.forEach((route) => { + assert.equal(inScope.has(route), true, `promoted route missing from in-scope: ${route}`); const contract = getRouteContract(route); - assert.equal(contract.scope, 'deferred'); + assert.equal(contract.scope, 'in-scope'); + assert.equal(contract.compatibilityPolicy, 'redirect_to_shell_when_spa_enabled'); }); }); diff --git a/gunicorn.conf.py b/gunicorn.conf.py index c39c509..56a31ae 100644 --- a/gunicorn.conf.py +++ b/gunicorn.conf.py @@ -5,14 +5,16 @@ workers = int(os.getenv("GUNICORN_WORKERS", "2")) # 2 workers for redundancy threads = int(os.getenv("GUNICORN_THREADS", "4")) worker_class = "gthread" -# Timeout settings - critical for dashboard stability -timeout = 65 # Worker timeout: must be > call_timeout (55s) -graceful_timeout = 30 # Graceful shutdown timeout (enough for thread cleanup) +# Timeout settings - critical for dashboard stability. +# Keep this above slow-query timeout paths (e.g. query-tool 120s) and DB pool timeout. +timeout = int(os.getenv("GUNICORN_TIMEOUT", "130")) +graceful_timeout = int(os.getenv("GUNICORN_GRACEFUL_TIMEOUT", "60")) keepalive = 5 # Keep-alive connections timeout -# Worker lifecycle management - prevent state accumulation -max_requests = 1000 # Restart worker after N requests -max_requests_jitter = 100 # Random jitter to prevent simultaneous restarts +# Worker lifecycle management - prevent state accumulation. +# Make these configurable so high-load test environments can raise the ceiling. +max_requests = int(os.getenv("GUNICORN_MAX_REQUESTS", "5000")) +max_requests_jitter = int(os.getenv("GUNICORN_MAX_REQUESTS_JITTER", "500")) # ============================================================ diff --git a/openspec/changes/deferred-route-modernization-follow-up/.openspec.yaml b/openspec/changes/archive/2026-02-12-deferred-route-modernization-follow-up/.openspec.yaml similarity index 100% rename from openspec/changes/deferred-route-modernization-follow-up/.openspec.yaml rename to openspec/changes/archive/2026-02-12-deferred-route-modernization-follow-up/.openspec.yaml diff --git a/openspec/changes/deferred-route-modernization-follow-up/design.md b/openspec/changes/archive/2026-02-12-deferred-route-modernization-follow-up/design.md similarity index 72% rename from openspec/changes/deferred-route-modernization-follow-up/design.md rename to openspec/changes/archive/2026-02-12-deferred-route-modernization-follow-up/design.md index 688ebb8..26bad0e 100644 --- a/openspec/changes/deferred-route-modernization-follow-up/design.md +++ b/openspec/changes/archive/2026-02-12-deferred-route-modernization-follow-up/design.md @@ -15,11 +15,13 @@ Current deferred-route risks: - Adopt canonical shell routing and explicit compatibility policy. - Execute contract-first content modernization with parity and rollback controls. - Enforce mandatory manual acceptance and BUG revalidation before sign-off. +- Enforce mandatory pre-change confirmation before each deferred-route implementation starts. **Non-Goals** - Reworking already in-scope phase-1 routes again. - Changing backend business data semantics beyond compatibility safeguards. - Bundling unrelated admin/report features into this follow-up. +- Restricting this change to already-`released` routes only (deferred routes are the intended modernization scope even when currently marked `dev`). ## Decisions @@ -38,6 +40,11 @@ Current deferred-route risks: - Known bug baseline must be recorded before implementation. - Reproduced known bugs on modernized path block sign-off and legacy retirement. +### D5. Pre-change confirmation is required per route +- Before changing `/tables`, `/excel-query`, `/query-tool`, or `/mid-section-defect`, maintainers must record route-scoped pre-change confirmation. +- Pre-change confirmation must include: current route status snapshot, scope boundary check, baseline contract/bug references, and planned rollback flag. +- Implementation work for a route is blocked until its pre-change confirmation is recorded. + ## Risks / Trade-offs - Deferred routes may have heavier legacy coupling than phase-1 routes. @@ -48,10 +55,11 @@ Current deferred-route risks: 1. Freeze deferred-route scope matrix for this follow-up change. 2. Extend shell route contracts and metadata coverage to full completeness. -3. Define route content contracts + golden fixtures + interaction parity checks. -4. Execute per-route migration with feature-flagged cutover and manual acceptance. -5. Retire deferred-route runtime fallback posture after acceptance and readiness gates pass. -6. Update runbook/rollback docs and close handoff linkage. +3. Record pre-change confirmation for the first deferred route to be modernized. +4. Define route content contracts + golden fixtures + interaction parity checks. +5. Execute per-route migration with feature-flagged cutover and manual acceptance. +6. Retire deferred-route runtime fallback posture after acceptance and readiness gates pass. +7. Update runbook/rollback docs and close handoff linkage. ## Rollback Strategy diff --git a/openspec/changes/deferred-route-modernization-follow-up/proposal.md b/openspec/changes/archive/2026-02-12-deferred-route-modernization-follow-up/proposal.md similarity index 85% rename from openspec/changes/deferred-route-modernization-follow-up/proposal.md rename to openspec/changes/archive/2026-02-12-deferred-route-modernization-follow-up/proposal.md index f5e1082..eb9eded 100644 --- a/openspec/changes/deferred-route-modernization-follow-up/proposal.md +++ b/openspec/changes/archive/2026-02-12-deferred-route-modernization-follow-up/proposal.md @@ -14,6 +14,7 @@ Those routes still run on legacy posture (direct-entry-first + fallback continui - Promote all deferred routes to first-class in-scope shell-governed targets. - Apply canonical shell routing policy and explicit direct-entry compatibility behavior for each deferred route. - Modernize deferred route page-content flow (filters/charts/interactions) with contract-first parity gates. +- Require route-by-route pre-change confirmation records before any implementation work starts on each deferred route. - Apply the same mandatory manual acceptance + BUG revalidation blocking policy used in phase 1. - Move deferred routes from fallback-era runtime posture to asset-readiness + governed retirement posture. @@ -31,3 +32,4 @@ Those routes still run on legacy posture (direct-entry-first + fallback continui - Shell contract and navigation governance in `frontend/src/portal-shell/**`. - Backend route handlers serving deferred routes and compatibility behavior. - Quality gate artifacts, runbook updates, and rollout/rollback policy for deferred-route cutover. +- Scope boundary clarification: this follow-up explicitly targets deferred routes (currently `dev` in page status) and does not require routes to already be `released` before modernization. diff --git a/openspec/changes/deferred-route-modernization-follow-up/specs/asset-readiness-and-fallback-retirement/spec.md b/openspec/changes/archive/2026-02-12-deferred-route-modernization-follow-up/specs/asset-readiness-and-fallback-retirement/spec.md similarity index 100% rename from openspec/changes/deferred-route-modernization-follow-up/specs/asset-readiness-and-fallback-retirement/spec.md rename to openspec/changes/archive/2026-02-12-deferred-route-modernization-follow-up/specs/asset-readiness-and-fallback-retirement/spec.md diff --git a/openspec/changes/deferred-route-modernization-follow-up/specs/page-content-modernization-safety/spec.md b/openspec/changes/archive/2026-02-12-deferred-route-modernization-follow-up/specs/page-content-modernization-safety/spec.md similarity index 56% rename from openspec/changes/deferred-route-modernization-follow-up/specs/page-content-modernization-safety/spec.md rename to openspec/changes/archive/2026-02-12-deferred-route-modernization-follow-up/specs/page-content-modernization-safety/spec.md index b5cf31c..a6509c3 100644 --- a/openspec/changes/deferred-route-modernization-follow-up/specs/page-content-modernization-safety/spec.md +++ b/openspec/changes/archive/2026-02-12-deferred-route-modernization-follow-up/specs/page-content-modernization-safety/spec.md @@ -7,6 +7,22 @@ Before deferred routes are cut over to modernized implementations, each route SH - **WHEN** `/tables`, `/excel-query`, `/query-tool`, or `/mid-section-defect` enters modernization - **THEN** a route-level baseline SHALL capture filter input semantics, query payload shape, and critical state expectations +### Requirement: Deferred-route implementation SHALL require pre-change confirmation +Each deferred route SHALL complete a route-scoped pre-change confirmation before implementation begins. + +#### Scenario: Route enters implementation queue +- **WHEN** a deferred route is selected for modernization implementation +- **THEN** a pre-change confirmation record SHALL exist before any route code changes proceed +- **THEN** the record SHALL include current route status snapshot, baseline contract references, known-bug baseline reference, and rollback flag plan + +### Requirement: Deferred-route modernization scope SHALL NOT be limited to already-released routes +Deferred modernization scope SHALL follow the deferred route matrix, even if those routes are currently marked `dev`. + +#### Scenario: Route status is dev in page registry +- **WHEN** `/tables`, `/excel-query`, `/query-tool`, or `/mid-section-defect` is currently `dev` +- **THEN** the route SHALL remain eligible for modernization in this follow-up change +- **THEN** already-`released` in-scope routes outside deferred scope SHALL not be reopened by this change unless explicitly required for shared governance wiring + ### Requirement: Deferred-route cutover SHALL require parity + manual acceptance Deferred routes SHALL NOT complete cutover without parity evidence and explicit manual sign-off. @@ -23,4 +39,3 @@ Known legacy bugs in deferred-route migrated scope SHALL be replayed during acce - **WHEN** deferred-route manual acceptance executes - **THEN** known-bug replay checks SHALL run - **THEN** reproduced known bugs SHALL fail route sign-off and block legacy retirement - diff --git a/openspec/changes/deferred-route-modernization-follow-up/specs/unified-shell-route-coverage/spec.md b/openspec/changes/archive/2026-02-12-deferred-route-modernization-follow-up/specs/unified-shell-route-coverage/spec.md similarity index 100% rename from openspec/changes/deferred-route-modernization-follow-up/specs/unified-shell-route-coverage/spec.md rename to openspec/changes/archive/2026-02-12-deferred-route-modernization-follow-up/specs/unified-shell-route-coverage/spec.md diff --git a/openspec/changes/archive/2026-02-12-deferred-route-modernization-follow-up/tasks.md b/openspec/changes/archive/2026-02-12-deferred-route-modernization-follow-up/tasks.md new file mode 100644 index 0000000..3c9a25b --- /dev/null +++ b/openspec/changes/archive/2026-02-12-deferred-route-modernization-follow-up/tasks.md @@ -0,0 +1,41 @@ +## 1. Scope and Governance Freeze + +- [x] 1.1 Publish frozen in-scope matrix for `/tables`, `/excel-query`, `/query-tool`, `/mid-section-defect` +- [x] 1.2 Define completion criteria, deprecation milestones, and exception registry updates for deferred-route phase +- [x] 1.3 Record explicit upstream linkage to `full-modernization-architecture-blueprint` handoff artifacts +- [x] 1.4 Add deferred-route pre-change confirmation template and required fields (status snapshot, contract baseline refs, known-bug baseline ref, rollback flag plan) +- [x] 1.5 Record scope boundary note that deferred/dev routes are in-scope for this change and released-only restriction does not apply + +## 2. Shell Route Contract Completion + +- [x] 2.1 Promote deferred routes to in-scope in shell route contracts with complete metadata +- [x] 2.2 Implement governed navigation targets and visibility policy validation for all deferred routes +- [x] 2.3 Add CI-blocking checks for missing deferred-route contract metadata in this phase + +## 3. Canonical Routing and Compatibility + +- [x] 3.1 Define canonical shell entry behavior for each deferred route +- [x] 3.2 Implement explicit compatibility policy for direct non-canonical entry with query continuity +- [x] 3.3 Add integration tests for canonical redirect and compatibility semantics + +## 4. Page-Content Modernization Safety + +- [x] 4.0 Before each route implementation starts, record and approve route-scoped pre-change confirmation +- [x] 4.1 Define per-route content contracts (filter semantics, payload, chart/table shape, state transitions) +- [x] 4.2 Build golden fixtures and interaction parity checks for each deferred route +- [x] 4.3 Add route-scoped feature flags and rollback controls for deferred-route cutover +- [x] 4.4 Define and enforce per-route manual acceptance checklist and sign-off records +- [x] 4.5 Record known-bug baselines before implementation and require bug replay during acceptance +- [x] 4.6 Block sign-off and legacy retirement when known bugs reproduce on modernized routes + +## 5. Asset Readiness and Fallback Retirement + +- [x] 5.1 Extend asset-readiness manifest/checks to deferred routes +- [x] 5.2 Enforce fail-fast release behavior when deferred-route assets are missing +- [x] 5.3 Retire deferred-route runtime fallback posture per governance milestones + +## 6. Quality Gates, CI, and Rollout + +- [x] 6.1 Extend functional/visual/accessibility/performance gates to deferred routes +- [x] 6.2 Wire CI jobs for route governance, quality gates, and readiness checks for deferred scope +- [x] 6.3 Update rollout runbook, rollback controls, and observability checkpoints for deferred-route cutover diff --git a/openspec/changes/deferred-route-modernization-follow-up/tasks.md b/openspec/changes/deferred-route-modernization-follow-up/tasks.md deleted file mode 100644 index 8f45e64..0000000 --- a/openspec/changes/deferred-route-modernization-follow-up/tasks.md +++ /dev/null @@ -1,38 +0,0 @@ -## 1. Scope and Governance Freeze - -- [ ] 1.1 Publish frozen in-scope matrix for `/tables`, `/excel-query`, `/query-tool`, `/mid-section-defect` -- [ ] 1.2 Define completion criteria, deprecation milestones, and exception registry updates for deferred-route phase -- [ ] 1.3 Record explicit upstream linkage to `full-modernization-architecture-blueprint` handoff artifacts - -## 2. Shell Route Contract Completion - -- [ ] 2.1 Promote deferred routes to in-scope in shell route contracts with complete metadata -- [ ] 2.2 Implement governed navigation targets and visibility policy validation for all deferred routes -- [ ] 2.3 Add CI-blocking checks for missing deferred-route contract metadata in this phase - -## 3. Canonical Routing and Compatibility - -- [ ] 3.1 Define canonical shell entry behavior for each deferred route -- [ ] 3.2 Implement explicit compatibility policy for direct non-canonical entry with query continuity -- [ ] 3.3 Add integration tests for canonical redirect and compatibility semantics - -## 4. Page-Content Modernization Safety - -- [ ] 4.1 Define per-route content contracts (filter semantics, payload, chart/table shape, state transitions) -- [ ] 4.2 Build golden fixtures and interaction parity checks for each deferred route -- [ ] 4.3 Add route-scoped feature flags and rollback controls for deferred-route cutover -- [ ] 4.4 Define and enforce per-route manual acceptance checklist and sign-off records -- [ ] 4.5 Record known-bug baselines before implementation and require bug replay during acceptance -- [ ] 4.6 Block sign-off and legacy retirement when known bugs reproduce on modernized routes - -## 5. Asset Readiness and Fallback Retirement - -- [ ] 5.1 Extend asset-readiness manifest/checks to deferred routes -- [ ] 5.2 Enforce fail-fast release behavior when deferred-route assets are missing -- [ ] 5.3 Retire deferred-route runtime fallback posture per governance milestones - -## 6. Quality Gates, CI, and Rollout - -- [ ] 6.1 Extend functional/visual/accessibility/performance gates to deferred routes -- [ ] 6.2 Wire CI jobs for route governance, quality gates, and readiness checks for deferred scope -- [ ] 6.3 Update rollout runbook, rollback controls, and observability checkpoints for deferred-route cutover diff --git a/openspec/specs/asset-readiness-and-fallback-retirement/spec.md b/openspec/specs/asset-readiness-and-fallback-retirement/spec.md index 3d8deed..220da06 100644 --- a/openspec/specs/asset-readiness-and-fallback-retirement/spec.md +++ b/openspec/specs/asset-readiness-and-fallback-retirement/spec.md @@ -19,13 +19,21 @@ Runtime fallback behavior for in-scope modernization routes SHALL be retired und - **THEN** runtime fallback behavior for that route SHALL be removed or disabled by policy - **THEN** reliability for that route SHALL be guaranteed by release-time readiness gates -### Requirement: Deferred routes SHALL keep existing fallback posture in this phase -Routes deferred from this modernization phase SHALL retain their existing fallback posture until handled by a follow-up change. +### Requirement: Deferred-route assets SHALL be release-ready before promotion +Deferred follow-up routes SHALL adopt release-time asset-readiness checks and SHALL fail promotion when required assets are missing. -#### Scenario: Deferred fallback continuity -- **WHEN** `/tables`, `/excel-query`, `/query-tool`, or `/mid-section-defect` is evaluated in this phase -- **THEN** fallback retirement SHALL NOT be required for phase completion -- **THEN** fallback retirement decisions for those routes SHALL be addressed in a follow-up modernization change +#### Scenario: Deferred-route readiness validation +- **WHEN** release artifacts are prepared for deferred-route promotion +- **THEN** required assets for `/tables`, `/excel-query`, `/query-tool`, and `/mid-section-defect` SHALL be validated +- **THEN** missing required assets SHALL fail release gating + +### Requirement: Deferred-route runtime fallback SHALL be retired by governed policy +Deferred follow-up routes SHALL not remain on runtime fallback posture after readiness, parity, and manual acceptance gates pass. + +#### Scenario: Deferred-route fallback retirement +- **WHEN** a deferred route passes readiness, parity, and manual acceptance gates +- **THEN** runtime fallback posture for that route SHALL be retired according to milestone policy +- **THEN** rollback control SHALL remain available via explicit route-level governance switch ### Requirement: Fallback-retirement failure response SHALL be consistent across route hosts When in-scope runtime fallback retirement is enabled and route assets are unavailable, app-level and blueprint-level route handlers SHALL return a consistent retired-fallback response surface. @@ -37,4 +45,3 @@ When in-scope runtime fallback retirement is enabled and route assets are unavai #### Scenario: Blueprint-level in-scope route enters retired fallback state - **WHEN** an in-scope blueprint-level route cannot serve required dist assets and fallback retirement is enabled - **THEN** the route SHALL return the same standardized retired-fallback response contract used by app-level routes - diff --git a/openspec/specs/page-content-modernization-safety/spec.md b/openspec/specs/page-content-modernization-safety/spec.md index c653536..5d8987e 100644 --- a/openspec/specs/page-content-modernization-safety/spec.md +++ b/openspec/specs/page-content-modernization-safety/spec.md @@ -11,6 +11,10 @@ Before chart/filter/page interaction refactors are cut over, each in-scope route - **THEN** the route SHALL define filter input semantics, query payload expectations, and chart data-shape contracts - **THEN** the route SHALL define critical state expectations for loading, empty, error, and success interactions +#### Scenario: Deferred-route contract baseline defined +- **WHEN** `/tables`, `/excel-query`, `/query-tool`, or `/mid-section-defect` enters modernization +- **THEN** a route-level baseline SHALL capture filter input semantics, query payload shape, and critical state expectations + ### Requirement: Cutover SHALL require parity evidence against baseline behavior In-scope chart/filter modernization cutover SHALL require parity evidence against baseline fixtures and critical interaction flows. @@ -35,6 +39,22 @@ In-scope chart/filter/page-content migration SHALL progress one route at a time - **THEN** that route SHALL be manually accepted using a defined checklist covering filter flows, chart interactions, empty/error behavior, and visual correctness - **THEN** the next route SHALL NOT begin cutover until manual acceptance for the current route is signed off +### Requirement: Deferred-route implementation SHALL require pre-change confirmation +Each deferred route SHALL complete a route-scoped pre-change confirmation before implementation begins. + +#### Scenario: Route enters implementation queue +- **WHEN** a deferred route is selected for modernization implementation +- **THEN** a pre-change confirmation record SHALL exist before any route code changes proceed +- **THEN** the record SHALL include current route status snapshot, baseline contract references, known-bug baseline reference, and rollback flag plan + +### Requirement: Deferred-route modernization scope SHALL NOT be limited to already-released routes +Deferred modernization scope SHALL follow the deferred route matrix, even if those routes are currently marked `dev`. + +#### Scenario: Route status is dev in page registry +- **WHEN** `/tables`, `/excel-query`, `/query-tool`, or `/mid-section-defect` is currently `dev` +- **THEN** the route SHALL remain eligible for modernization in this follow-up change +- **THEN** already-`released` in-scope routes outside deferred scope SHALL not be reopened by this change unless explicitly required for shared governance wiring + ### Requirement: Known legacy bugs in migrated scope SHALL NOT be carried into modernized routes Modernized route acceptance SHALL include explicit revalidation of known legacy defects in migrated scope, and reproduced defects SHALL block sign-off. @@ -43,6 +63,11 @@ Modernized route acceptance SHALL include explicit revalidation of known legacy - **THEN** a route-level known-bug baseline (within migrated scope) SHALL be recorded before implementation - **THEN** manual acceptance SHALL replay those known-bug checks on the modernized route +#### Scenario: Deferred-route bug replay gate +- **WHEN** deferred-route manual acceptance executes +- **THEN** known-bug replay checks SHALL run +- **THEN** reproduced known bugs SHALL fail route sign-off and block legacy retirement + #### Scenario: Legacy bug carry-over is blocked - **WHEN** manual acceptance finds that a known legacy bug is still reproducible in the modernized route - **THEN** route sign-off SHALL fail @@ -55,4 +80,3 @@ Legacy chart/filter implementations SHALL be removed only after parity checks an - **WHEN** legacy chart/filter code is planned for removal on an in-scope route - **THEN** the route SHALL provide parity pass evidence and manual acceptance sign-off records - **THEN** unresolved parity failures or manual acceptance defects SHALL block legacy removal - diff --git a/openspec/specs/unified-shell-route-coverage/spec.md b/openspec/specs/unified-shell-route-coverage/spec.md index c64cf3a..817029b 100644 --- a/openspec/specs/unified-shell-route-coverage/spec.md +++ b/openspec/specs/unified-shell-route-coverage/spec.md @@ -4,7 +4,7 @@ TBD - created by archiving change full-modernization-architecture-blueprint. Update Purpose after archive. ## Requirements ### Requirement: In-scope routes SHALL be shell-contract governed -All in-scope modernization routes SHALL be represented in shell route contracts, loader registration policy, and navigation visibility governance. +All in-scope modernization routes, including deferred follow-up routes, SHALL be represented in shell route contracts, loader registration policy, and navigation visibility governance with complete metadata. #### Scenario: In-scope coverage validation - **WHEN** shell route contract validation is executed @@ -15,13 +15,10 @@ All in-scope modernization routes SHALL be represented in shell route contracts, - **WHEN** shell navigation is built for admin users - **THEN** `/admin/pages` and `/admin/performance` SHALL be represented as governed navigation targets according to visibility/access policy -### Requirement: Out-of-scope routes SHALL not block this phase -Routes explicitly marked as out-of-scope for this modernization phase SHALL be excluded from required shell-coverage gates in this phase. - -#### Scenario: Deferred route exclusion -- **WHEN** modernization gates execute for this phase -- **THEN** `/tables`, `/excel-query`, `/query-tool`, and `/mid-section-defect` SHALL be treated as deferred routes -- **THEN** deferred route absence from new shell-governance gates SHALL NOT fail this phase +#### Scenario: Deferred route contract promotion +- **WHEN** follow-up route coverage validation is executed +- **THEN** `/tables`, `/excel-query`, `/query-tool`, and `/mid-section-defect` SHALL have route metadata, owner metadata, and visibility/access policy metadata +- **THEN** missing metadata on those deferred routes SHALL fail route-governance validation ### Requirement: Route coverage governance SHALL be CI-enforced Route coverage and contract completeness checks for in-scope routes SHALL run as CI gates. @@ -48,4 +45,3 @@ When contract loading falls back from the primary modernization contract artifac #### Scenario: Legacy contract fallback path selected - **WHEN** the primary contract artifact is unavailable and a legacy contract file is loaded - **THEN** the system SHALL log a warning that includes the selected legacy source path - diff --git a/scripts/check_full_modernization_gates.py b/scripts/check_full_modernization_gates.py index 08d0974..6704f97 100755 --- a/scripts/check_full_modernization_gates.py +++ b/scripts/check_full_modernization_gates.py @@ -88,6 +88,10 @@ def _route_css_targets() -> dict[str, list[Path]]: "/tmtt-defect": [ROOT / "frontend/src/tmtt-defect/style.css"], "/admin/pages": [ROOT / "src/mes_dashboard/templates/admin/pages.html"], "/admin/performance": [ROOT / "src/mes_dashboard/templates/admin/performance.html"], + "/tables": [ROOT / "frontend/src/tables/style.css"], + "/excel-query": [ROOT / "frontend/src/excel-query/style.css"], + "/query-tool": [ROOT / "frontend/src/query-tool/style.css"], + "/mid-section-defect": [ROOT / "frontend/src/mid-section-defect/style.css"], } @@ -129,9 +133,9 @@ def _check_scope_matrix(scope_matrix: dict[str, Any], report: CheckReport) -> tu report.fail("scope matrix has no in-scope routes") if "/admin/pages" not in in_scope or "/admin/performance" not in in_scope: report.fail("scope matrix must include /admin/pages and /admin/performance") - required_deferred = {"/tables", "/excel-query", "/query-tool", "/mid-section-defect"} + required_deferred: set[str] = set() if deferred != required_deferred: - report.fail("scope matrix deferred routes mismatch expected policy") + report.fail("scope matrix deferred routes mismatch expected policy (all routes promoted to in-scope)") return in_scope, deferred diff --git a/src/mes_dashboard/app.py b/src/mes_dashboard/app.py index 5e43e8c..d223841 100644 --- a/src/mes_dashboard/app.py +++ b/src/mes_dashboard/app.py @@ -676,6 +676,10 @@ def create_app(config_name: str | None = None) -> Flask: @app.route('/tables') def tables_page(): """Table viewer page served as pure Vite HTML output.""" + canonical_redirect = maybe_redirect_to_canonical_shell('/tables') + if canonical_redirect is not None: + return canonical_redirect + dist_dir = os.path.join(app.static_folder or "", "dist") dist_html = os.path.join(dist_dir, "tables.html") if os.path.exists(dist_html): @@ -687,14 +691,14 @@ def create_app(config_name: str | None = None) -> Flask: return send_from_directory(nested_dist_dir, "index.html") # Test/local fallback when frontend build artifacts are absent. - return ( + return missing_in_scope_asset_response('/tables', ( "" "" "MES 數據表查詢工具" "" "
", 200, - ) + )) @app.route('/wip-overview') def wip_overview_page(): @@ -765,6 +769,9 @@ def create_app(config_name: str | None = None) -> Flask: @app.route('/excel-query') def excel_query_page(): """Excel batch query tool page.""" + canonical_redirect = maybe_redirect_to_canonical_shell('/excel-query') + if canonical_redirect is not None: + return canonical_redirect return render_template('excel_query.html') @app.route('/resource-history') @@ -821,8 +828,23 @@ def create_app(config_name: str | None = None) -> Flask: @app.route('/mid-section-defect') def mid_section_defect_page(): """Mid-section defect traceability analysis page (pure Vite).""" + canonical_redirect = maybe_redirect_to_canonical_shell('/mid-section-defect') + if canonical_redirect is not None: + return canonical_redirect + dist_dir = os.path.join(app.static_folder or "", "dist") - return send_from_directory(dist_dir, 'mid-section-defect.html') + dist_html = os.path.join(dist_dir, "mid-section-defect.html") + if os.path.exists(dist_html): + return send_from_directory(dist_dir, 'mid-section-defect.html') + + return missing_in_scope_asset_response('/mid-section-defect', ( + "" + "" + "中段製程不良追溯" + "" + "
", + 200, + )) # ======================================================== # Table Query APIs (for table_data_viewer) diff --git a/src/mes_dashboard/routes/query_tool_routes.py b/src/mes_dashboard/routes/query_tool_routes.py index 5af926a..dbea159 100644 --- a/src/mes_dashboard/routes/query_tool_routes.py +++ b/src/mes_dashboard/routes/query_tool_routes.py @@ -11,6 +11,7 @@ Contains Flask Blueprint for batch tracing and equipment period query endpoints: from flask import Blueprint, jsonify, request, Response, render_template +from mes_dashboard.core.modernization_policy import maybe_redirect_to_canonical_shell from mes_dashboard.services.query_tool_service import ( resolve_lots, get_lot_history, @@ -43,6 +44,9 @@ query_tool_bp = Blueprint('query_tool', __name__) @query_tool_bp.route('/query-tool') def query_tool_page(): """Render the query tool page.""" + canonical_redirect = maybe_redirect_to_canonical_shell('/query-tool') + if canonical_redirect is not None: + return canonical_redirect return render_template('query_tool.html') diff --git a/tests/e2e/test_admin_auth_e2e.py b/tests/e2e/test_admin_auth_e2e.py index e3d9923..675acd4 100644 --- a/tests/e2e/test_admin_auth_e2e.py +++ b/tests/e2e/test_admin_auth_e2e.py @@ -89,12 +89,17 @@ class TestFullLoginLogoutFlow: def test_complete_admin_login_workflow(self, mock_auth, _mock_is_admin, client): """Test complete admin login workflow.""" mock_auth.return_value = _mock_admin_user() - - # 1. Access portal - should see login link - response = client.get("/") - assert response.status_code == 200 - content = response.data.decode("utf-8") - assert "管理員登入" in content + spa_enabled = bool(client.application.config.get("PORTAL_SPA_ENABLED", False)) + + # 1. Access portal entry. + response = client.get("/", follow_redirects=False) + if spa_enabled: + assert response.status_code == 302 + assert response.location.rstrip("/").endswith("/portal-shell") + else: + assert response.status_code == 200 + content = response.data.decode("utf-8") + assert "管理員登入" in content # 2. Go to login page response = client.get("/admin/login") @@ -106,15 +111,21 @@ class TestFullLoginLogoutFlow: "password": "password123" }, follow_redirects=True) - assert response.status_code == 200 - content = response.data.decode("utf-8") - # Should see admin name and logout option - assert "Test Admin" in content or "登出" in content - - # 4. Verify session has admin - with client.session_transaction() as sess: - assert "admin" in sess - assert sess["admin"]["mail"] == "ymirliu@panjit.com.tw" + assert response.status_code == 200 + if not spa_enabled: + content = response.data.decode("utf-8") + # Portal template should show admin identity in non-SPA mode. + assert "Test Admin" in content or "登出" in content + + # 4. Verify session has admin + with client.session_transaction() as sess: + assert "admin" in sess + assert sess["admin"]["mail"] == "ymirliu@panjit.com.tw" + + if spa_enabled: + nav = client.get("/api/portal/navigation") + assert nav.status_code == 200 + assert nav.get_json()["is_admin"] is True # 5. Access admin pages response = client.get("/admin/pages") @@ -244,20 +255,22 @@ class TestPageManagementFlow: class TestPortalDynamicTabs: - """E2E tests for dynamic portal tabs based on page status.""" - - def test_portal_hides_dev_tabs_for_non_admin(self, client, temp_page_status): - """Test portal hides dev page tabs for non-admin users.""" - response = client.get("/") - assert response.status_code == 200 - content = response.data.decode("utf-8") - - # Released pages should show - assert "WIP 即時概況" in content - - # Dev pages should NOT show (tables and resource are dev) - # Note: This depends on the can_view_page implementation in portal.html - + """E2E tests for dynamic portal tabs based on page status.""" + + def test_portal_hides_dev_tabs_for_non_admin(self, client, temp_page_status): + """Test portal hides dev page tabs for non-admin users.""" + response = client.get("/api/portal/navigation") + assert response.status_code == 200 + payload = response.get_json() + routes = { + page["route"] + for drawer in payload.get("drawers", []) + for page in drawer.get("pages", []) + } + assert "/wip-overview" in routes + assert "/tables" not in routes + assert payload.get("is_admin") is False + @patch('mes_dashboard.routes.auth_routes.is_admin', return_value=True) @patch('mes_dashboard.routes.auth_routes.authenticate') def test_portal_shows_all_tabs_for_admin(self, mock_auth, _mock_is_admin, client, temp_page_status): @@ -270,12 +283,17 @@ class TestPortalDynamicTabs: "password": "password123" }) - response = client.get("/") - assert response.status_code == 200 - content = response.data.decode("utf-8") - - # Admin should see all pages - assert "WIP 即時概況" in content + response = client.get("/api/portal/navigation") + assert response.status_code == 200 + payload = response.get_json() + routes = { + page["route"] + for drawer in payload.get("drawers", []) + for page in drawer.get("pages", []) + } + assert "/wip-overview" in routes + assert "/tables" in routes + assert payload.get("is_admin") is True class TestSessionPersistence: diff --git a/tests/e2e/test_resource_history_e2e.py b/tests/e2e/test_resource_history_e2e.py index fa61a8c..26f359c 100644 --- a/tests/e2e/test_resource_history_e2e.py +++ b/tests/e2e/test_resource_history_e2e.py @@ -37,27 +37,44 @@ def client(app): class TestResourceHistoryPageAccess: """E2E tests for page access and navigation.""" + @staticmethod + def _load_resource_history_entry(client): + spa_enabled = bool(client.application.config.get("PORTAL_SPA_ENABLED", False)) + response = client.get('/resource-history', follow_redirects=False) + if spa_enabled: + assert response.status_code == 302 + assert response.location.endswith('/portal-shell/resource-history') + shell_response = client.get('/portal-shell/resource-history') + assert shell_response.status_code == 200 + return shell_response, True + return response, False + def test_page_loads_successfully(self, client): """Resource history page should load without errors.""" - response = client.get('/resource-history') - + response, spa_enabled = self._load_resource_history_entry(client) assert response.status_code == 200 content = response.data.decode('utf-8') - assert '設備歷史績效' in content + if spa_enabled: + assert '/static/dist/portal-shell.js' in content + else: + assert '設備歷史績效' in content def test_page_bootstrap_container_exists(self, client): """Resource history page should expose the Vue mount container.""" - response = client.get('/resource-history') + response, _spa_enabled = self._load_resource_history_entry(client) content = response.data.decode('utf-8') assert "id='app'" in content or 'id="app"' in content def test_page_references_vite_module(self, client): """Resource history page should load the Vite module bundle.""" - response = client.get('/resource-history') + response, spa_enabled = self._load_resource_history_entry(client) content = response.data.decode('utf-8') - assert '/static/dist/resource-history.js' in content + if spa_enabled: + assert '/static/dist/portal-shell.js' in content + else: + assert '/static/dist/resource-history.js' in content assert 'type="module"' in content @@ -329,16 +346,28 @@ class TestResourceHistoryValidation: assert response.status_code == 200, f"Failed for granularity={granularity}" -class TestResourceHistoryNavigation: - """E2E tests for navigation integration.""" - - def test_portal_includes_history_tab(self, client): - """Portal should include resource history tab.""" - response = client.get('/') - content = response.data.decode('utf-8') - - assert '設備歷史績效' in content - assert 'resourceHistoryFrame' in content +class TestResourceHistoryNavigation: + """E2E tests for navigation integration.""" + + def test_portal_includes_history_tab(self, client): + """Portal should include resource history tab.""" + if bool(client.application.config.get("PORTAL_SPA_ENABLED", False)): + response = client.get('/api/portal/navigation') + assert response.status_code == 200 + payload = response.get_json() + pages = [ + page + for drawer in payload.get("drawers", []) + for page in drawer.get("pages", []) + ] + history_pages = [page for page in pages if page.get("route") == "/resource-history"] + assert history_pages, "resource-history route missing from portal navigation contract" + assert history_pages[0].get("name") == "設備歷史績效" + else: + response = client.get('/') + content = response.data.decode('utf-8') + assert '設備歷史績效' in content + assert 'resourceHistoryFrame' in content if __name__ == '__main__': diff --git a/tests/stress/test_api_load.py b/tests/stress/test_api_load.py index 62534a2..cda4aec 100644 --- a/tests/stress/test_api_load.py +++ b/tests/stress/test_api_load.py @@ -24,11 +24,11 @@ from urllib.parse import quote class TestAPILoadConcurrent: """Load tests with concurrent requests.""" - def _make_request(self, url: str, timeout: float) -> Tuple[bool, float, str]: - """Make a single request and return (success, duration, error).""" - start = time.time() - try: - response = requests.get(url, timeout=timeout) + def _make_request(self, url: str, timeout: float, headers: dict | None = None) -> Tuple[bool, float, str]: + """Make a single request and return (success, duration, error).""" + start = time.time() + try: + response = requests.get(url, timeout=timeout, headers=headers) duration = time.time() - start if response.status_code == 200: data = response.json() @@ -199,7 +199,7 @@ class TestAPILoadConcurrent: assert result.success_rate >= 85.0, f"Success rate {result.success_rate:.1f}% is below 85%" assert result.avg_response_time < 20.0, f"Avg response time {result.avg_response_time:.2f}s exceeds 20s" - def test_resource_summary_concurrent_load(self, base_url: str, stress_config: dict, stress_result): + def test_resource_summary_concurrent_load(self, base_url: str, stress_config: dict, stress_result): """Test resource status summary API under concurrent load.""" result = stress_result("Resource Status Summary Concurrent Load") url = f"{base_url}/api/resource/status/summary" @@ -209,12 +209,17 @@ class TestAPILoadConcurrent: total_requests = concurrent_users * requests_per_user - start_time = time.time() - with concurrent.futures.ThreadPoolExecutor(max_workers=concurrent_users) as executor: - futures = [ - executor.submit(self._make_request, url, timeout) - for _ in range(total_requests) - ] + start_time = time.time() + with concurrent.futures.ThreadPoolExecutor(max_workers=concurrent_users) as executor: + futures = [ + executor.submit( + self._make_request, + url, + timeout, + {"X-Forwarded-For": f"10.0.0.{(idx % concurrent_users) + 1}"}, + ) + for idx in range(total_requests) + ] for future in concurrent.futures.as_completed(futures): success, duration, error = future.result() @@ -247,12 +252,13 @@ class TestAPILoadConcurrent: total_requests = concurrent_users * len(endpoints) * requests_per_endpoint start_time = time.time() - with concurrent.futures.ThreadPoolExecutor(max_workers=concurrent_users) as executor: - futures = [] - for _ in range(concurrent_users): - for endpoint in endpoints: - for _ in range(requests_per_endpoint): - futures.append(executor.submit(self._make_request, endpoint, timeout)) + with concurrent.futures.ThreadPoolExecutor(max_workers=concurrent_users) as executor: + futures = [] + for user_idx in range(concurrent_users): + headers = {"X-Forwarded-For": f"10.0.1.{user_idx + 1}"} + for endpoint in endpoints: + for _ in range(requests_per_endpoint): + futures.append(executor.submit(self._make_request, endpoint, timeout, headers)) for future in concurrent.futures.as_completed(futures): success, duration, error = future.result() diff --git a/tests/test_asset_readiness_policy.py b/tests/test_asset_readiness_policy.py index 3c7f521..65f18af 100644 --- a/tests/test_asset_readiness_policy.py +++ b/tests/test_asset_readiness_policy.py @@ -3,6 +3,8 @@ from __future__ import annotations +import json +from pathlib import Path from unittest.mock import patch import pytest @@ -57,7 +59,7 @@ def test_hold_blueprints_share_retired_fallback_template(monkeypatch, route, exi assert "系統發生錯誤" in response.data.decode("utf-8") -def test_deferred_route_keeps_fallback_posture_when_in_scope_retirement_enabled(monkeypatch): +def test_promoted_deferred_route_enforces_asset_readiness_when_retirement_enabled(monkeypatch): monkeypatch.setenv("PORTAL_SPA_ENABLED", "false") monkeypatch.setenv("MODERNIZATION_RETIRE_IN_SCOPE_RUNTIME_FALLBACK", "true") app = create_app("testing") @@ -69,4 +71,20 @@ def test_deferred_route_keeps_fallback_posture_when_in_scope_retirement_enabled( with patch("mes_dashboard.app.os.path.exists", return_value=False): response = client.get("/tables") - assert response.status_code == 200 + # Promoted deferred routes now enforce asset readiness (503 when missing). + assert response.status_code == 503 + + +def test_template_backed_promoted_routes_do_not_require_html_assets(): + manifest_path = ( + Path(__file__).resolve().parents[1] + / "docs" + / "migration" + / "full-modernization-architecture-blueprint" + / "asset_readiness_manifest.json" + ) + payload = json.loads(manifest_path.read_text(encoding="utf-8")) + required = payload.get("in_scope_required_assets", {}) + + assert required.get("/excel-query") == ["excel-query.js"] + assert required.get("/query-tool") == ["query-tool.js"] diff --git a/tests/test_full_modernization_gates.py b/tests/test_full_modernization_gates.py index b5d24e1..179643b 100644 --- a/tests/test_full_modernization_gates.py +++ b/tests/test_full_modernization_gates.py @@ -50,10 +50,13 @@ def test_gate_runner_block_mode_passes_current_baseline(): assert payload["passed"] is True -def test_scope_matrix_keeps_deferred_routes_for_follow_up_only(): +def test_scope_matrix_has_all_routes_promoted_to_in_scope(): matrix = json.loads(SCOPE_MATRIX.read_text(encoding="utf-8")) deferred = {item["route"] for item in matrix["deferred"]} - assert deferred == {"/tables", "/excel-query", "/query-tool", "/mid-section-defect"} + assert deferred == set(), "all deferred routes should be promoted to in-scope" + in_scope_routes = {item["route"] for item in matrix["in_scope"]} + for route in ("/tables", "/excel-query", "/query-tool", "/mid-section-defect"): + assert route in in_scope_routes, f"{route} should be in-scope" def test_route_contract_parity_check_detects_route_set_drift(): diff --git a/tests/test_portal_shell_routes.py b/tests/test_portal_shell_routes.py index 851cc53..fc4002b 100644 --- a/tests/test_portal_shell_routes.py +++ b/tests/test_portal_shell_routes.py @@ -419,14 +419,23 @@ def test_legacy_shell_contract_fallback_logs_warning(monkeypatch): app_module._SHELL_ROUTE_CONTRACT_MAP = None -def test_deferred_routes_keep_direct_entry_compatibility_when_spa_enabled(monkeypatch): +def test_promoted_deferred_routes_redirect_to_canonical_shell_when_spa_enabled(monkeypatch): monkeypatch.setenv("PORTAL_SPA_ENABLED", "true") app = create_app("testing") app.config["TESTING"] = True client = app.test_client() _login_as_admin(client) - for route in ["/excel-query", "/query-tool", "/tables"]: - response = client.get(route, follow_redirects=False) - # Deferred routes stay on direct-entry posture in this phase. - assert response.status_code == 200, route + cases = { + "/tables?category=wip": "/portal-shell/tables?category=wip", + "/excel-query": "/portal-shell/excel-query", + "/query-tool": "/portal-shell/query-tool", + "/mid-section-defect": "/portal-shell/mid-section-defect", + } + + for direct_url, canonical_url in cases.items(): + response = client.get(direct_url, follow_redirects=False) + assert response.status_code == 302, f"{direct_url} should redirect" + assert response.location.endswith(canonical_url), ( + f"{direct_url}: expected {canonical_url}, got {response.location}" + ) diff --git a/tests/test_portal_shell_wave_b_native_smoke.py b/tests/test_portal_shell_wave_b_native_smoke.py index 11a40bf..9b5142c 100644 --- a/tests/test_portal_shell_wave_b_native_smoke.py +++ b/tests/test_portal_shell_wave_b_native_smoke.py @@ -50,8 +50,9 @@ def test_job_query_native_smoke_query_search_export(client): shell = client.get("/portal-shell/job-query?start_date=2026-02-01&end_date=2026-02-11") assert shell.status_code == 200 - page = client.get("/job-query") - assert page.status_code == 200 + page = client.get("/job-query", follow_redirects=False) + assert page.status_code == 302 + assert page.location.endswith("/portal-shell/job-query") with ( patch( @@ -111,8 +112,9 @@ def test_excel_query_native_smoke_upload_detect_query_export(client): shell = client.get("/portal-shell/excel-query?mode=upload") assert shell.status_code == 200 - page = client.get("/excel-query") - assert page.status_code == 200 + page = client.get("/excel-query", follow_redirects=False) + assert page.status_code == 302 + assert page.location.endswith("/portal-shell/excel-query") from mes_dashboard.routes.excel_query_routes import _uploaded_excel_cache @@ -183,8 +185,9 @@ def test_query_tool_native_smoke_resolve_history_association(client): shell = client.get("/portal-shell/query-tool?input_type=lot_id") assert shell.status_code == 200 - page = client.get("/query-tool") - assert page.status_code == 200 + page = client.get("/query-tool", follow_redirects=False) + assert page.status_code == 302 + assert page.location.endswith("/portal-shell/query-tool") with ( patch( @@ -224,11 +227,17 @@ def test_query_tool_native_smoke_resolve_history_association(client): def test_tmtt_defect_native_smoke_range_query_and_csv_export(client): + _login_as_admin(client) + shell = client.get("/portal-shell/tmtt-defect?start_date=2026-02-01&end_date=2026-02-11") assert shell.status_code == 200 - page = client.get("/tmtt-defect") - assert page.status_code == 200 + page = client.get("/tmtt-defect", follow_redirects=False) + if client.application.config.get("PORTAL_SPA_ENABLED", False): + assert page.status_code == 302 + assert page.location.endswith("/portal-shell/tmtt-defect") + else: + assert page.status_code == 200 with ( patch( diff --git a/tests/test_query_tool_routes.py b/tests/test_query_tool_routes.py index 479c0ea..e07853c 100644 --- a/tests/test_query_tool_routes.py +++ b/tests/test_query_tool_routes.py @@ -28,17 +28,19 @@ def client(app): return app.test_client() -class TestQueryToolPage: - """Tests for /query-tool page route.""" - - def test_page_returns_html(self, client): - """Should return the query tool page.""" - with client.session_transaction() as sess: - sess["admin"] = {"username": "admin", "displayName": "Admin"} - - response = client.get('/query-tool') - assert response.status_code == 200 - assert b'html' in response.data.lower() +class TestQueryToolPage: + """Tests for /query-tool page route.""" + + def test_page_returns_html(self, client, monkeypatch): + """Should return the query tool page when SPA redirect is disabled.""" + monkeypatch.setenv("PORTAL_SPA_ENABLED", "false") + client.application.config["PORTAL_SPA_ENABLED"] = False + with client.session_transaction() as sess: + sess["admin"] = {"username": "admin", "displayName": "Admin"} + + response = client.get('/query-tool') + assert response.status_code == 200 + assert b'html' in response.data.lower() class TestResolveEndpoint: diff --git a/tests/test_template_integration.py b/tests/test_template_integration.py index 184d391..8d2a595 100644 --- a/tests/test_template_integration.py +++ b/tests/test_template_integration.py @@ -303,10 +303,15 @@ class TestMesApiUsageInTemplates(unittest.TestCase): self.assertTrue('MesApi.get' in html or '/static/dist/wip-detail.js' in html) def test_tables_page_uses_mesapi_or_vite_module(self): - response = self.client.get('/tables') - html = response.data.decode('utf-8') + response, final_response, html = _get_response_and_html(self.client, '/tables') - self.assertTrue('MesApi.post' in html or '/static/dist/tables.js' in html) + if response.status_code == 302: + self.assertTrue(response.location.endswith('/portal-shell/tables')) + self.assertEqual(final_response.status_code, 200) + self.assertIn('/static/dist/portal-shell.js', html) + else: + self.assertEqual(response.status_code, 200) + self.assertTrue('MesApi.post' in html or '/static/dist/tables.js' in html) def test_resource_page_uses_mesapi_or_vite_module(self): response, final_response, html = _get_response_and_html(self.client, '/resource') @@ -324,11 +329,16 @@ class TestMesApiUsageInTemplates(unittest.TestCase): ) def test_query_tool_page_uses_vite_module(self): - response = self.client.get('/query-tool') - html = response.data.decode('utf-8') + response, final_response, html = _get_response_and_html(self.client, '/query-tool') - self.assertIn('/static/dist/query-tool.js', html) - self.assertIn('type="module"', html) + if response.status_code == 302: + self.assertTrue(response.location.endswith('/portal-shell/query-tool')) + self.assertEqual(final_response.status_code, 200) + self.assertIn('/static/dist/portal-shell.js', html) + else: + self.assertEqual(response.status_code, 200) + self.assertIn('/static/dist/query-tool.js', html) + self.assertIn('type="module"', html) def test_tmtt_defect_page_uses_vite_module(self): response, final_response, html = _get_response_and_html(self.client, '/tmtt-defect') @@ -377,6 +387,9 @@ class TestViteModuleIntegration(unittest.TestCase): '/resource-history': '/portal-shell/resource-history', '/job-query': '/portal-shell/job-query', '/tmtt-defect': '/portal-shell/tmtt-defect', + '/tables': '/portal-shell/tables', + '/excel-query': '/portal-shell/excel-query', + '/query-tool': '/portal-shell/query-tool', } for endpoint, asset in endpoints_and_assets: