feat(modernization): promote deferred routes to in-scope and unify page header styles

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 <noreply@anthropic.com>
This commit is contained in:
egg
2026-02-12 13:20:06 +08:00
parent 0ed69ce326
commit c38b5f646a
67 changed files with 1073 additions and 252 deletions

View File

@@ -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)
# ============================================================

View File

@@ -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:

View File

@@ -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.

View File

@@ -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"
}
]
}

View File

@@ -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

View File

@@ -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.

View File

@@ -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: /<route-name>
Status Snapshot: <dev|released>
Scope Boundary Check: confirmed in route_scope_matrix.json as in-scope
Contract Baseline Refs:
- Route contract: route_contracts.json#/<route-name>
- Content contract: route_content_contracts.json#/<route-name>
Known-Bug Baseline Ref: known_bug_baseline.json#/<route-name>
Rollback Flag Plan:
- Feature flag: modernization_feature_flags.json#/<route-name>.content_cutover_enabled
- Rollback strategy: fallback_to_legacy_route
Owner: <owner>
Date: <YYYY-MM-DD>
Approved By: <reviewer>
```

View File

@@ -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"
}
]
}

View File

@@ -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.

View File

@@ -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.

View File

@@ -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"
}
]
}

View File

@@ -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.

View File

@@ -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"
]
}

View File

@@ -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": []
}

View File

@@ -19,6 +19,10 @@
"/resource-history",
"/qc-gate",
"/job-query",
"/tmtt-defect"
"/tmtt-defect",
"/tables",
"/excel-query",
"/query-tool",
"/mid-section-defect"
]
}

View File

@@ -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"
}
]
}

View File

@@ -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"]
}
}

View File

@@ -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": {

View File

@@ -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"]
}
}

View File

@@ -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": []
}

View File

@@ -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
}

View File

@@ -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"]
}
}
}

View File

@@ -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",

View File

@@ -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": []
}

View File

@@ -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"],

View File

@@ -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"
}
]
}

View File

@@ -57,7 +57,6 @@ onMounted(async () => {
<div class="excel-query-page u-content-shell">
<header class="excel-query-header">
<h1>Excel 批次查詢</h1>
<p>Native Route-ViewUpload / Detect / Query / Export</p>
</header>
<div class="u-panel-stack">

View File

@@ -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);
}

View File

@@ -1,7 +1,7 @@
@import '../wip-shared/styles.css';
.hold-detail-page .header h1 {
font-size: 20px;
font-size: 24px;
}
.hold-type-badge {

View File

@@ -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 {

View File

@@ -1,5 +1,5 @@
.hold-overview-page .header h1 {
font-size: 22px;
font-size: 24px;
}
.hold-type-badge {

View File

@@ -61,7 +61,6 @@ onMounted(async () => {
<div class="job-query-page u-content-shell">
<header class="job-query-header">
<h1>設備維修查詢</h1>
<p>Native Route-View查詢 / 交易歷程 / CSV 匯出</p>
</header>
<div class="u-panel-stack">

View File

@@ -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));
}

View File

@@ -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);
}

View File

@@ -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',
}),
});

View File

@@ -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;
}

View File

@@ -60,7 +60,6 @@ onMounted(async () => {
<div class="query-tool-page u-content-shell">
<header class="query-tool-header">
<h1>批次追蹤工具</h1>
<p>Native Route-ViewResolve / History / Association / Equipment Period</p>
</header>
<div class="u-panel-stack">

View File

@@ -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);
}

View File

@@ -1,5 +1,5 @@
.history-header {
background: linear-gradient(135deg, #4f46e5 0%, #0ea5e9 100%);
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.filter-row {

View File

@@ -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);
}

View File

@@ -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 {

View File

@@ -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));
}

View File

@@ -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');
});
});

View File

@@ -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"))
# ============================================================

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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', (
"<!doctype html><html lang=\"zh-Hant\"><head><meta charset=\"UTF-8\">"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
"<title>MES 數據表查詢工具</title>"
"<script type=\"module\" src=\"/static/dist/tables.js\"></script>"
"</head><body><div id='app'></div></body></html>",
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,9 +828,24 @@ 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")
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', (
"<!doctype html><html lang=\"zh-Hant\"><head><meta charset=\"UTF-8\">"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
"<title>中段製程不良追溯</title>"
"<script type=\"module\" src=\"/static/dist/mid-section-defect.js\"></script>"
"</head><body><div id='app'></div></body></html>",
200,
))
# ========================================================
# Table Query APIs (for table_data_viewer)
# ========================================================

View File

@@ -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')

View File

@@ -89,9 +89,14 @@ 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()
spa_enabled = bool(client.application.config.get("PORTAL_SPA_ENABLED", False))
# 1. Access portal - should see login link
response = client.get("/")
# 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
@@ -107,8 +112,9 @@ class TestFullLoginLogoutFlow:
}, follow_redirects=True)
assert response.status_code == 200
if not spa_enabled:
content = response.data.decode("utf-8")
# Should see admin name and logout option
# Portal template should show admin identity in non-SPA mode.
assert "Test Admin" in content or "登出" in content
# 4. Verify session has admin
@@ -116,6 +122,11 @@ class TestFullLoginLogoutFlow:
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")
assert response.status_code == 200
@@ -248,15 +259,17 @@ class TestPortalDynamicTabs:
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("/")
response = client.get("/api/portal/navigation")
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
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')
@@ -270,12 +283,17 @@ class TestPortalDynamicTabs:
"password": "password123"
})
response = client.get("/")
response = client.get("/api/portal/navigation")
assert response.status_code == 200
content = response.data.decode("utf-8")
# Admin should see all pages
assert "WIP 即時概況" in content
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:

View File

@@ -37,26 +37,43 @@ 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')
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')
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
@@ -334,9 +351,21 @@ class TestResourceHistoryNavigation:
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

View File

@@ -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]:
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)
response = requests.get(url, timeout=timeout, headers=headers)
duration = time.time() - start
if response.status_code == 200:
data = response.json()
@@ -212,8 +212,13 @@ class TestAPILoadConcurrent:
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)
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):
@@ -249,10 +254,11 @@ class TestAPILoadConcurrent:
start_time = time.time()
with concurrent.futures.ThreadPoolExecutor(max_workers=concurrent_users) as executor:
futures = []
for _ in range(concurrent_users):
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))
futures.append(executor.submit(self._make_request, endpoint, timeout, headers))
for future in concurrent.futures.as_completed(futures):
success, duration, error = future.result()

View File

@@ -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"]

View File

@@ -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():

View File

@@ -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}"
)

View File

@@ -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,10 +227,16 @@ 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")
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 (

View File

@@ -31,8 +31,10 @@ def client(app):
class TestQueryToolPage:
"""Tests for /query-tool page route."""
def test_page_returns_html(self, client):
"""Should return the query tool page."""
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"}

View File

@@ -303,9 +303,14 @@ 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')
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):
@@ -324,9 +329,14 @@ 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')
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)
@@ -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: