feat(modernization): full architecture blueprint with hardening follow-up

Implement phased modernization infrastructure for transitioning from
multi-page legacy routing to SPA portal-shell architecture, plus
post-delivery hardening fixes for policy loading, fallback consistency,
and governance drift detection.

Key changes:
- Add route contract enrichment with scope/visibility/compatibility policies
- Canonical 302 redirects from legacy direct-entry to /portal-shell/ routes
- Asset readiness enforcement and runtime fallback retirement for in-scope routes
- Shared feature-flag helpers (env > config > default) replacing duplicated _to_bool
- Defensive copy for lru_cached policy payloads preventing mutation corruption
- Unified retired-fallback response helper across app and blueprint routes
- Frontend/backend route-contract cross-validation in governance gates
- Shell CSS token fallback values for routes rendered outside shell scope
- Local-safe .env.example defaults with production recommendation comments
- Legacy contract fallback warning logging and single-hop redirect optimization

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
egg
2026-02-12 11:26:02 +08:00
parent 2c8d80afe6
commit 7cb0985b12
113 changed files with 4577 additions and 582 deletions

View File

@@ -0,0 +1,40 @@
# asset-readiness-and-fallback-retirement Specification
## Purpose
TBD - created by archiving change full-modernization-architecture-blueprint. Update Purpose after archive.
## Requirements
### Requirement: In-scope frontend assets SHALL be release-ready before deployment
In-scope routes SHALL rely on build/deploy readiness guarantees instead of runtime fallback behavior as the primary resilience mechanism.
#### Scenario: Build-readiness enforcement
- **WHEN** release artifacts are prepared for deployment
- **THEN** in-scope route assets SHALL be validated for presence and loadability
- **THEN** missing required in-scope assets SHALL fail the release gate
### Requirement: Runtime fallback retirement SHALL follow a governed phase policy
Runtime fallback behavior for in-scope modernization routes SHALL be retired under explicit governance milestones.
#### Scenario: Fallback retirement in phase scope
- **WHEN** a route is marked in-scope for fallback retirement
- **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.
#### 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
### 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.
#### Scenario: App-level in-scope route enters retired fallback state
- **WHEN** an in-scope app-level route cannot serve required dist assets and fallback retirement is enabled
- **THEN** the route SHALL return the standardized retired-fallback response contract
#### 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

@@ -0,0 +1,48 @@
# frontend-platform-modernization-governance Specification
## Purpose
TBD - created by archiving change full-modernization-architecture-blueprint. Update Purpose after archive.
## Requirements
### Requirement: Frontend modernization scope SHALL be explicitly governed
The modernization program SHALL define an explicit in-scope and out-of-scope route matrix for each phase, and SHALL treat that matrix as a release-governed contract artifact.
#### Scenario: Scope matrix publication
- **WHEN** a modernization phase is created
- **THEN** the phase SHALL publish an explicit in-scope route list and out-of-scope route list
- **THEN** the matrix SHALL include `/admin/pages` and `/admin/performance` in scope for this phase
- **THEN** the matrix SHALL mark `/tables`, `/excel-query`, `/query-tool`, and `/mid-section-defect` as deferred routes for a follow-up phase
#### Scenario: Scope drift prevention
- **WHEN** implementation tasks are derived from the phase specs
- **THEN** tasks targeting routes outside the in-scope matrix SHALL be rejected for this phase
### Requirement: Modernization phases SHALL define completion and deprecation milestones
Each modernization phase SHALL define measurable completion criteria and deprecation milestones for legacy-era patterns.
#### Scenario: Phase completion criteria
- **WHEN** a phase reaches release review
- **THEN** it SHALL provide objective completion criteria for route governance, style governance, and quality gates
- **THEN** it SHALL identify any remaining deferred routes and their next-phase linkage
#### Scenario: Legacy deprecation milestones
- **WHEN** legacy fallback or legacy style exceptions remain in phase scope
- **THEN** the phase SHALL define a dated milestone or release gate to remove those exceptions
### Requirement: Operator-facing environment defaults SHALL be onboarding-safe
`.env.example` SHALL prioritize local onboarding safety while clearly documenting production hardening recommendations for modernization controls.
#### Scenario: Local bootstrap from `.env.example`
- **WHEN** a developer initializes `.env` from `.env.example` in a local non-production environment
- **THEN** startup-critical modernization flags SHALL default to onboarding-safe values that do not fail boot solely because dist readiness gates are strict by default
#### Scenario: Production recommendation visibility
- **WHEN** operators review `.env.example` for deployment configuration
- **THEN** production-recommended values for shell-first and modernization-hardening flags SHALL be explicitly documented in adjacent comments
### Requirement: Policy cache refresh model SHALL be explicit in governance docs
Governance-owned policy artifacts that are loaded with in-process caching SHALL document runtime refresh behavior and operator expectations.
#### Scenario: Cached policy artifact behavior documentation
- **WHEN** maintainers read modernization governance artifacts
- **THEN** they SHALL find explicit guidance on whether policy JSON updates require process restart, cache clear, or automatic reload

View File

@@ -0,0 +1,29 @@
# frontend-quality-gate-modernization Specification
## Purpose
TBD - created by archiving change full-modernization-architecture-blueprint. Update Purpose after archive.
## Requirements
### Requirement: Modernization releases SHALL pass multi-dimensional frontend quality gates
In-scope modernization releases SHALL pass functional, visual, accessibility, and performance gates before promotion.
#### Scenario: Gate bundle at release candidate
- **WHEN** a release candidate includes in-scope modernization changes
- **THEN** it SHALL execute functional behavior parity checks for affected routes
- **THEN** it SHALL execute critical-state visual regression checks for affected routes
- **THEN** it SHALL execute accessibility checks for keyboard and reduced-motion behavior
- **THEN** it SHALL execute performance budget checks for defined shell/route thresholds
### Requirement: Gate failures SHALL block release promotion
Blocking quality gates SHALL prevent release promotion for in-scope modernization changes.
#### Scenario: Blocking gate failure
- **WHEN** any mandatory modernization quality gate fails
- **THEN** release promotion SHALL be blocked until the failure is resolved or explicitly waived per governance policy
### Requirement: Deferred routes SHALL be excluded from this phase gate baseline
The route baseline for this modernization phase SHALL exclude deferred routes.
#### Scenario: Deferred route baseline exclusion
- **WHEN** gate baseline is computed for this phase
- **THEN** `/tables`, `/excel-query`, `/query-tool`, and `/mid-section-defect` SHALL be excluded from mandatory modernization gate coverage

View File

@@ -2,22 +2,27 @@
Define stable requirements for full-vite-page-modularization.
## Requirements
### Requirement: Major Pages SHALL be Managed by Vite Modules
The system SHALL provide Vite-managed module entries for major portal pages under a phased SPA-shell migration while keeping direct route access compatible.
The system SHALL provide Vite-managed module entries for all in-scope modernization routes under shell-first governance, including admin surfaces `/admin/pages` and `/admin/performance` as governed targets. Deferred routes (`/tables`, `/excel-query`, `/query-tool`, `/mid-section-defect`) are excluded from this phase's required module-governance completeness.
#### Scenario: Portal shell module loading
- **WHEN** the portal experience is rendered
- **THEN** it MUST load its behavior from a Vite-built module asset when available
#### Scenario: In-scope module governance completeness
- **WHEN** modernization route coverage is validated for this phase
- **THEN** every in-scope route SHALL have deterministic module-governance metadata and ownership mapping
#### Scenario: Module fallback continuity
- **WHEN** a required Vite asset is unavailable
- **THEN** the system MUST keep affected page behavior functional through explicit fallback logic
#### Scenario: Deferred route exclusion in this phase
- **WHEN** completeness validation executes for this phase
- **THEN** deferred routes SHALL be excluded from mandatory pass criteria
### Requirement: Build Pipeline SHALL Produce Backend-Served Assets
Vite build output MUST be emitted into backend static paths and served by Flask/Gunicorn on the same origin.
Vite build output for in-scope modernization routes MUST be emitted into backend static paths and validated at release time. Missing required in-scope assets SHALL fail release gates instead of relying on runtime fallback behavior.
#### Scenario: Build artifact placement
- **WHEN** frontend build is executed
- **THEN** generated JS/CSS files SHALL be written to the configured backend static dist directory
#### Scenario: Build artifact readiness for in-scope routes
- **WHEN** frontend build is executed for release
- **THEN** required in-scope route artifacts SHALL be present in configured backend static dist paths
- **THEN** missing required artifacts SHALL fail readiness checks
#### Scenario: Deferred route fallback posture unchanged in this phase
- **WHEN** deferred routes are evaluated in this phase
- **THEN** existing fallback posture SHALL not block this phase's completion
### Requirement: Vite Page Modules SHALL Reuse Shared Chart and Query Building Blocks
Page entry modules MUST consume shared chart/query/drawer utilities for common behaviors.
@@ -83,3 +88,4 @@ WIP overview and WIP detail Vite entry modules SHALL use shared frontend core ut
#### Scenario: Shared utility change propagates across both pages
- **WHEN** autocomplete mapping rules are updated in the shared core module
- **THEN** both WIP overview and WIP detail modules MUST consume the updated behavior without duplicated page-local logic edits

View File

@@ -17,3 +17,21 @@ Cache, throttling, and index-related numeric literals that control behavior MUST
- **WHEN** operators need to tune cache/index thresholds
- **THEN** they MUST find values in named constants or environment variables rather than scattered inline literals
### Requirement: Feature-flag resolution SHALL use shared helper semantics
Environment/config/default feature-flag resolution logic SHALL be implemented through shared helper utilities instead of duplicated per-module parsing.
#### Scenario: Feature-flag evaluation in app and policy modules
- **WHEN** modules resolve boolean feature flags from environment variables and Flask config
- **THEN** they SHALL use a shared helper that enforces consistent precedence and truthy/falsey parsing behavior
### Requirement: Cached policy payloads SHALL protect against shared mutable-state corruption
Policy loader functions that cache JSON payloads in-process SHALL prevent downstream callers from mutating the shared cached object reference.
#### Scenario: Cached policy payload consumed by multiple callers
- **WHEN** multiple callers read cached policy payloads during process lifetime
- **THEN** one caller's accidental mutation SHALL NOT alter another caller's observed policy state through shared reference side effects
#### Scenario: Policy cache behavior documentation
- **WHEN** maintainers inspect cached policy loader code
- **THEN** they SHALL find explicit comments describing refresh/invalidation behavior expectations

View File

@@ -0,0 +1,58 @@
# page-content-modernization-safety Specification
## Purpose
TBD - created by archiving change full-modernization-architecture-blueprint. Update Purpose after archive.
## Requirements
### Requirement: In-scope page-content modernization SHALL be contract-first
Before chart/filter/page interaction refactors are cut over, each in-scope route SHALL define a contract baseline that captures data and interaction semantics.
#### Scenario: Route contract baseline defined
- **WHEN** an in-scope route is selected for chart/filter modernization
- **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
### 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.
#### Scenario: Parity gate before default switch
- **WHEN** a route is proposed for defaulting to a modernized chart/filter implementation
- **THEN** golden fixture parity checks SHALL pass for defined critical states
- **THEN** interaction parity checks SHALL pass for filter apply/reset and chart selection/drill behaviors
### Requirement: Route-level content cutover SHALL be reversible
Modernized chart/filter content rollouts SHALL use reversible controls that allow immediate rollback without reverting unrelated shell architecture work.
#### Scenario: Controlled rollout and rollback
- **WHEN** a modernized route is enabled for users
- **THEN** the route SHALL be controlled by route-scoped feature flag or equivalent switch
- **THEN** rollback procedure SHALL be documented and executable within one release cycle
### Requirement: Page-content modernization progression SHALL require manual route acceptance
In-scope chart/filter/page-content migration SHALL progress one route at a time with explicit manual acceptance records.
#### Scenario: Route-by-route manual acceptance gate
- **WHEN** an in-scope route completes modernization implementation and parity checks
- **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: 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.
#### Scenario: Route-level legacy bug baseline and replay
- **WHEN** an in-scope route enters chart/filter/page-content modernization
- **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: 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
- **THEN** route cutover completion and legacy code retirement SHALL be blocked until the bug is fixed
### Requirement: Legacy content path retirement SHALL require parity and manual acceptance
Legacy chart/filter implementations SHALL be removed only after parity checks and manual acceptance criteria are satisfied.
#### Scenario: Legacy removal approval
- **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

@@ -2,46 +2,31 @@
Define stable requirements for spa-shell-navigation.
## Requirements
### Requirement: Portal SHALL provide a SPA shell driven by Vue Router
The portal frontend SHALL use a single SPA shell entry and Vue Router to render page modules without iframe embedding, and SHALL route each page through native route-view integration. The shell layout SHALL use a full-viewport fluid layout with flexbox, removing all max-width constraints and block-centered styling. The main content area (`.shell-content`) SHALL fill available space as a flex child, and the sidebar SHALL be a collapsible flex child that pushes content when expanded on desktop. The content area class SHALL be `.shell-content` (not `.content`) to avoid CSS collision with page-level `.content` classes.
The portal frontend SHALL use a single SPA shell entry and Vue Router to render in-scope page modules without iframe embedding. In-scope routes for this phase SHALL include the governed report routes and admin surfaces `/admin/pages` and `/admin/performance`, while deferred routes (`/tables`, `/excel-query`, `/query-tool`, `/mid-section-defect`) are explicitly excluded from this phase contract.
#### Scenario: Drawer navigation renders integrated route view
- **WHEN** a user clicks a sidebar page entry whose migration mode is `native`
- **THEN** the active route SHALL be updated through Vue Router
- **THEN** the main content area SHALL render the corresponding page module inside shell route-view without iframe usage
- **THEN** the content area SHALL fill the available viewport width minus the sidebar width (if sidebar is expanded)
#### Scenario: In-scope route renders through shell governance
- **WHEN** a user navigates to an in-scope shell-governed route
- **THEN** the route SHALL resolve through Vue Router with shell contract metadata
- **THEN** the shell SHALL render the corresponding module/target without iframe fallback
#### Scenario: Shell layout fills full viewport
- **WHEN** the portal shell renders
- **THEN** the shell SHALL span the full viewport width with no max-width constraint
- **THEN** the header SHALL span edge-to-edge with no border-radius
- **THEN** the sidebar and content area SHALL have no outer borders or border-radius
#### Scenario: Admin route appears as governed target
- **WHEN** an admin user opens shell navigation
- **THEN** `/admin/pages` and `/admin/performance` SHALL be exposed as governed navigation targets per access policy
#### Scenario: Page-level max-width constraints are removed when embedded
- **WHEN** a page module registered in the shell route contracts renders inside `.shell-content`
- **THEN** page-level max-width constraints SHALL be overridden to allow full-width rendering
- **THEN** page-level duplicate padding SHALL be removed to avoid double spacing
- **THEN** standalone page rendering (outside the shell) SHALL remain unaffected
#### Scenario: Shell content class avoids collision with page-level classes
- **WHEN** a page module that uses its own `.content` class renders inside the shell
- **THEN** the shell's content wrapper (`.shell-content`) SHALL NOT interfere with the page's `.content` styling
#### Scenario: Wrapper route remains available during migration
- **WHEN** a user clicks a sidebar page entry whose migration mode is `wrapper`
- **THEN** Vue Router SHALL render the wrapper host in shell content area
- **THEN** the wrapper SHALL preserve page reachability until native rewrite is completed
#### Scenario: Deferred route is excluded from this phase route-governance requirement
- **WHEN** phase-level shell-governance compliance is evaluated
- **THEN** `/tables`, `/excel-query`, `/query-tool`, and `/mid-section-defect` SHALL be treated as deferred and excluded from pass/fail criteria for this phase
### Requirement: Existing route contracts SHALL remain stable in SPA mode
Migration to SPA shell SHALL preserve existing route paths, deep-link behavior, and query semantics during both native and wrapper phases.
Migration to the shell-first SPA model SHALL preserve route/query compatibility for in-scope routes while introducing canonical shell routing policy and explicit compatibility handling.
#### Scenario: Direct route entry remains functional
- **WHEN** a user opens an existing route directly (bookmark or refresh)
- **THEN** the route SHALL resolve to the same page functionality as before migration
- **THEN** required query parameters SHALL continue to be interpreted with compatible semantics
#### Scenario: Canonical shell path behavior for in-scope routes
- **WHEN** a user opens an in-scope report route via canonical shell path
- **THEN** route behavior and query semantics SHALL remain compatible with established baseline behavior
#### Scenario: Query continuity across shell navigation
- **WHEN** users navigate from shell list pages to detail pages and back
- **THEN** query-state parameters required by list/detail workflows SHALL remain consistent with pre-migration behavior
#### Scenario: Compatibility policy for direct route entry
- **WHEN** a user opens an in-scope report route via direct non-canonical entry
- **THEN** the system SHALL apply explicit compatibility policy (preserve behavior or compatibility redirect) without breaking route semantics
### Requirement: SPA shell navigation SHALL enforce page visibility rules
SPA navigation SHALL respect backend-defined drawer and page visibility outcomes, including admin entry visibility and route fallback for hidden routes.
@@ -60,3 +45,22 @@ SPA navigation SHALL respect backend-defined drawer and page visibility outcomes
- **THEN** the shell SHALL redirect to a safe fallback route
- **THEN** the shell SHALL NOT expose iframe-based fallback rendering
### Requirement: Canonical redirect scope boundaries SHALL be explicit and intentional
Canonical shell direct-entry redirects SHALL apply only to governed in-scope report routes and SHALL explicitly exclude admin external targets with documented rationale.
#### Scenario: In-scope report route direct entry
- **WHEN** SPA shell mode is enabled and a user enters an in-scope report route directly
- **THEN** the system SHALL redirect to the canonical `/portal-shell/...` route while preserving query semantics
#### Scenario: Admin external target direct entry
- **WHEN** SPA shell mode is enabled and a user enters `/admin/pages` or `/admin/performance` directly
- **THEN** the system SHALL NOT apply report-route canonical redirect policy
- **THEN** the exclusion rationale SHALL be documented in code-level comments or governance docs
### Requirement: Missing-required-parameter redirects SHALL avoid avoidable multi-hop chains
Routes with server-side required query parameters SHALL minimize redirect hops under SPA shell mode.
#### Scenario: Hold detail missing reason in SPA shell mode
- **WHEN** a user opens `/hold-detail` without `reason` while SPA shell mode is enabled
- **THEN** the route SHALL resolve via a single-hop redirect to the canonical overview shell path

View File

@@ -0,0 +1,40 @@
# style-isolation-and-token-enforcement Specification
## Purpose
TBD - created by archiving change full-modernization-architecture-blueprint. Update Purpose after archive.
## Requirements
### Requirement: In-scope pages SHALL enforce style isolation boundaries
In-scope modernization pages SHALL avoid page-global selectors for page-local concerns and SHALL keep style concerns scoped to route-level containers or shared design-system layers.
#### Scenario: Global selector control
- **WHEN** style governance checks analyze in-scope page styles
- **THEN** page-local style changes SHALL NOT introduce new `:root` or `body` rules for route-local presentation concerns
- **THEN** shared cross-route concerns SHALL be authored in designated shared style layers
### Requirement: In-scope shared semantics SHALL be token-first
Shared UI semantics in in-scope routes SHALL be implemented with token-backed Tailwind/shared-style primitives before page-local overrides are allowed.
#### Scenario: Token-first UI pattern adoption
- **WHEN** an in-scope route introduces or updates shared UI semantics (layout shell, card, filter, action, status)
- **THEN** the route SHALL consume token-backed shared primitives
- **THEN** page-local hard-coded visual values SHALL require explicit exception justification
### Requirement: Legacy style exceptions SHALL be tracked and sunset
Legacy CSS exceptions for in-scope routes SHALL be tracked with ownership and removal milestones.
#### Scenario: Exception registry requirement
- **WHEN** an in-scope route cannot yet remove legacy style behavior
- **THEN** the route SHALL be registered with an exception owner and planned removal milestone
- **THEN** unresolved exceptions past milestone SHALL fail modernization governance review
### Requirement: Route-local token usage SHALL include fallback values outside shell scope
Route-level styles that reference shell-provided token variables SHALL define fallback values to preserve rendering correctness when rendered outside shell variable scope.
#### Scenario: Route rendered outside portal shell variable scope
- **WHEN** a route-local stylesheet references shell token variables and the page is rendered without shell-level CSS variables
- **THEN** visual-critical properties (for example header gradients) SHALL still resolve through explicit fallback token values
#### Scenario: Style governance check for unresolved shell variables
- **WHEN** style-governance validation inspects in-scope route styles
- **THEN** unresolved shell-variable references without fallback SHALL be flagged as governance failures or approved exceptions

View File

@@ -1,29 +1,28 @@
## Purpose
Define stable requirements for tailwind-design-system.
## Requirements
### Requirement: Frontend styles SHALL be governed by Tailwind design tokens
The frontend SHALL define a Tailwind-based design token system for color, spacing, typography, radius, and elevation to ensure consistent styling across modules. The `--portal-shell-max-width` CSS variable SHALL be set to `none` to support the fluid layout. A `--portal-sidebar-width` variable SHALL be added for sidebar width reference. The `.u-content-shell` utility class SHALL use `width: 100%` instead of `max-width` constraint.
The frontend SHALL enforce a token-governed style system for in-scope routes. Shared visual semantics SHALL be expressed through token-backed Tailwind/shared layers, and ad-hoc page-local hard-coded values for shared semantics SHALL require explicit exception governance.
#### Scenario: Shared token usage across modules
- **WHEN** two report modules render equivalent UI elements (e.g., card, filter chip, primary button)
#### Scenario: Shared token usage across in-scope modules
- **WHEN** two in-scope modules render equivalent UI semantics (e.g., card, filter chip, primary action, status indicator)
- **THEN** they SHALL use the same token-backed style semantics
- **THEN** visual output SHALL remain consistent across modules
- **THEN** visual output SHALL remain consistent across those modules
#### Scenario: Fluid layout tokens
- **WHEN** the portal shell renders
- **THEN** `--portal-shell-max-width` SHALL resolve to `none`
- **THEN** `--portal-sidebar-width` SHALL resolve to `240px`
- **THEN** `.u-content-shell` SHALL apply `width: 100%` without max-width constraint
#### Scenario: Token governance review
- **WHEN** an in-scope route introduces new shared UI styling
- **THEN** the styling SHALL map to shared tokens/layers or be recorded in an approved exception registry
### Requirement: Tailwind migration SHALL support coexistence with legacy CSS
The migration SHALL allow Tailwind and existing page CSS to coexist during phased rollout without breaking existing pages.
Tailwind migration SHALL support controlled coexistence only as a transition state for this phase. In-scope routes SHALL move toward isolation-first style ownership and SHALL NOT introduce new page-global CSS side effects for route-local concerns.
#### Scenario: Legacy page remains functional during coexistence
- **WHEN** a not-yet-migrated page is rendered
- **THEN** existing CSS behavior SHALL remain intact
- **THEN** Tailwind introduction SHALL NOT cause blocking style regressions
#### Scenario: In-scope global selector control
- **WHEN** in-scope route styles are reviewed
- **THEN** new route-local styling SHALL NOT introduce page-global selectors (`:root`, `body`) for local presentation behavior
#### Scenario: Deferred route coexistence allowance
- **WHEN** deferred routes (`/tables`, `/excel-query`, `/query-tool`, `/mid-section-defect`) are evaluated during this phase
- **THEN** existing coexistence posture SHALL be allowed and handled by a follow-up modernization change
### Requirement: New shared UI components SHALL prefer Tailwind-first styling
Newly introduced shared components SHALL be implemented with Tailwind-first conventions to avoid expanding duplicated page-local CSS.
@@ -32,3 +31,4 @@ Newly introduced shared components SHALL be implemented with Tailwind-first conv
- **WHEN** a new shared component is introduced in migration scope
- **THEN** its primary style contract SHALL be expressed through Tailwind utilities/components
- **THEN** page-local CSS additions SHALL be minimized and justified

View File

@@ -0,0 +1,51 @@
# unified-shell-route-coverage Specification
## Purpose
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.
#### Scenario: In-scope coverage validation
- **WHEN** shell route contract validation is executed
- **THEN** every in-scope route SHALL have route metadata, ownership metadata, and visibility policy metadata
- **THEN** missing in-scope route contracts SHALL fail validation
#### Scenario: Admin route inclusion
- **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
### Requirement: Route coverage governance SHALL be CI-enforced
Route coverage and contract completeness checks for in-scope routes SHALL run as CI gates.
#### Scenario: CI gate failure on in-scope gap
- **WHEN** CI detects an in-scope route without required contract metadata
- **THEN** the modernization gate SHALL fail
- **THEN** release promotion SHALL be blocked until resolved
### Requirement: Frontend and backend route-contract inventories SHALL be cross-validated
Route-governance checks SHALL verify that frontend shell route contracts and backend route contract artifacts describe the same governed route set and scope classes.
#### Scenario: Cross-source contract parity gate
- **WHEN** modernization governance checks run in CI
- **THEN** mismatches between backend route contract JSON and frontend `routeContracts.js` route inventory SHALL fail the gate
#### Scenario: Scope classification drift detection
- **WHEN** a route has inconsistent scope classification between frontend and backend contract sources
- **THEN** governance checks SHALL report the specific route and conflicting scope values
### Requirement: Legacy contract-source fallback SHALL emit operational warning
When contract loading falls back from the primary modernization contract artifact to a legacy artifact path, the service SHALL emit explicit warning telemetry.
#### 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

@@ -1,135 +1,139 @@
## Purpose
Define stable requirements for vue-vite-page-architecture.
## Requirements
### Requirement: Pure Vite pages SHALL be served as static HTML
The system SHALL support serving Vite-built HTML pages directly via Flask without Jinja2 rendering.
#### Scenario: Serve pure Vite page
- **WHEN** user navigates to a pure Vite page route (e.g., `/qc-gate`)
- **THEN** Flask SHALL serve the pre-built HTML file from `static/dist/` via `send_from_directory`
- **THEN** the HTML SHALL NOT pass through Jinja2 template rendering
#### Scenario: Page works as top-level navigation target
- **WHEN** a pure Vite page is opened from portal direct navigation
- **THEN** the page SHALL render correctly as a top-level route without iframe embedding dependency
- **THEN** page functionality SHALL NOT rely on portal-managed frame lifecycle
### Requirement: Vite config SHALL support Vue SFC and HTML entry points
The Vite build configuration SHALL support Vue Single File Components alongside existing vanilla JS entries.
#### Scenario: Vue plugin coexistence
- **WHEN** `vite build` is executed
- **THEN** Vue SFC (`.vue` files) SHALL be compiled by `@vitejs/plugin-vue`
- **THEN** existing vanilla JS entry points SHALL continue to build without modification
#### Scenario: HTML entry point
- **WHEN** a page uses an HTML file as its Vite entry point
- **THEN** Vite SHALL process the HTML and its referenced JS/CSS into `static/dist/`
- **THEN** the output SHALL include `<page-name>.html`, `<page-name>.js`, and `<page-name>.css`
#### Scenario: Chunk splitting
- **WHEN** Vite builds the project
- **THEN** Vue runtime SHALL be split into a `vendor-vue` chunk
- **THEN** ECharts modules (including TreemapChart, BarChart, LineChart) SHALL be split into the existing `vendor-echarts` chunk
- **THEN** chunk splitting SHALL NOT affect existing page bundles
#### Scenario: Migrated page entry replacement
- **WHEN** a vanilla JS page is migrated to Vue 3
- **THEN** its Vite entry SHALL change from JS file to HTML file (e.g., `src/wip-overview/main.js``src/wip-overview/index.html`)
- **THEN** the original JS entry SHALL be replaced, not kept alongside
#### Scenario: Hold Overview entry point
- **WHEN** the hold-overview page is added
- **THEN** `vite.config.js` input SHALL include `'hold-overview': resolve(__dirname, 'src/hold-overview/index.html')`
- **THEN** the build SHALL produce `hold-overview.html`, `hold-overview.js`, and `hold-overview.css` in `static/dist/`
#### Scenario: Hold History entry point
- **WHEN** the hold-history page is added
- **THEN** `vite.config.js` input SHALL include `'hold-history': resolve(__dirname, 'src/hold-history/index.html')`
- **THEN** the build SHALL produce `hold-history.html`, `hold-history.js`, and `hold-history.css` in `static/dist/`
#### Scenario: Shared CSS import across migrated pages
- **WHEN** multiple migrated pages import a shared CSS module (e.g., `wip-shared/styles.css`)
- **THEN** Vite SHALL bundle the shared CSS into each page's output CSS
- **THEN** shared CSS SHALL NOT create a separate shared chunk that requires additional HTTP requests
#### Scenario: Shared composable import across module boundaries
- **WHEN** a migrated page imports a composable from another shared module (e.g., `hold-history` imports `useAutoRefresh` from `wip-shared/`)
- **THEN** the composable SHALL be bundled into the importing page's JS output
- **THEN** cross-module imports SHALL NOT create unexpected shared chunks
### Requirement: Pure Vite pages SHALL handle API calls without legacy MesApi
Pure Vite pages SHALL use the existing `frontend/src/core/api.js` module for API communication without depending on the global `window.MesApi` object from `_base.html`.
#### Scenario: API GET request from pure Vite page
- **WHEN** a pure Vite page makes a GET API call
- **THEN** the call SHALL use the `apiGet` function from `core/api.js`
- **THEN** the call SHALL work without `window.MesApi` being present
### Requirement: Pure Vite pages SHALL handle POST API calls without legacy MesApi
Pure Vite pages SHALL use the `apiPost` function from `core/api.js` for POST requests without depending on `window.MesApi`.
#### Scenario: API POST request from pure Vite page
- **WHEN** a pure Vite page makes a POST API call
- **THEN** the call SHALL use the `apiPost` function from `core/api.js`
- **THEN** the call SHALL include `Content-Type: application/json` header
- **THEN** the call SHALL work without `window.MesApi` being present
#### Scenario: CSRF token handling in POST requests
- **WHEN** a pure Vite page calls `apiPost`
- **THEN** `apiPost` SHALL attempt to read CSRF token from `<meta name="csrf-token">`
- **THEN** if no meta tag exists, the request SHALL still proceed (non-admin APIs do not enforce CSRF)
### Requirement: Pure Vite pages with server-side route validation SHALL use send_from_directory with pre-validation
Pages that require server-side parameter validation before serving SHALL validate parameters in the Flask route and then serve the static HTML.
#### Scenario: Hold Detail reason validation
- **WHEN** user navigates to `/hold-detail` without a `reason` parameter
- **THEN** Flask SHALL redirect to `/wip-overview`
- **WHEN** user navigates to `/hold-detail?reason={value}`
- **THEN** Flask SHALL serve the pre-built HTML file from `static/dist/` via `send_from_directory`
- **THEN** the HTML SHALL NOT pass through Jinja2 template rendering
#### Scenario: Frontend fallback validation
- **WHEN** the pure Vite hold-detail page loads
- **THEN** the page SHALL read `reason` from URL parameters
- **THEN** if `reason` is empty or missing, the page SHALL redirect to `/wip-overview`
### Requirement: Mid-section defect page SHALL separate filter state from query state
The mid-section defect page SHALL maintain separate reactive state for UI input (`filters`) and committed query parameters (`committedFilters`).
#### Scenario: User changes date without clicking query
- **WHEN** user modifies the date range in the filter bar but does not click "查詢"
- **THEN** auto-refresh, pagination, and CSV export SHALL continue using the previously committed filter values
- **THEN** the new date range SHALL NOT affect any API calls until "查詢" is clicked
#### Scenario: User clicks query button
- **WHEN** user clicks "查詢"
- **THEN** the current `filters` state SHALL be snapshotted into `committedFilters`
- **THEN** all subsequent API calls SHALL use the committed values
#### Scenario: CSV export uses committed filters
- **WHEN** user clicks "匯出 CSV" after modifying filters without re-querying
- **THEN** the export SHALL use the committed filter values from the last query
- **THEN** the export SHALL NOT use the current UI filter values
### Requirement: Mid-section defect page SHALL cancel in-flight requests on new query
The mid-section defect page SHALL use `AbortController` to cancel in-flight API requests when a new query is initiated.
#### Scenario: New query cancels previous query
- **WHEN** user clicks "查詢" while a previous query is still in-flight
- **THEN** the previous query's summary and detail requests SHALL be aborted
- **THEN** the AbortError SHALL be handled silently (no error banner shown)
#### Scenario: Page navigation cancels previous detail request
- **WHEN** user clicks next page while a previous page request is still in-flight
- **THEN** the previous page request SHALL be aborted
- **THEN** the new page request SHALL proceed independently
#### Scenario: Query and pagination use independent abort keys
- **WHEN** a query is in-flight and user triggers pagination
- **THEN** the query SHALL NOT be cancelled by the pagination request
- **THEN** the pagination SHALL use a separate abort key from the query
## Purpose
Define stable requirements for vue-vite-page-architecture.
## Requirements
### Requirement: Pure Vite pages SHALL be served as static HTML
The system SHALL serve in-scope pure Vite pages through backend static HTML delivery under a shell-first canonical routing policy. Direct-entry compatibility for in-scope routes SHALL be explicit and governed. Admin targets `/admin/pages` and `/admin/performance` SHALL be represented as governed shell navigation targets, while maintaining backend auth/session authority.
#### Scenario: In-scope canonical shell entry
- **WHEN** a user navigates to an in-scope canonical shell route
- **THEN** the shell SHALL render the target route via governed route contracts and static asset delivery
#### Scenario: Direct-entry compatibility policy for in-scope routes
- **WHEN** a user opens an in-scope route through direct non-canonical entry
- **THEN** the system SHALL apply explicit compatibility behavior without breaking established query semantics
#### Scenario: Admin targets in shell governance
- **WHEN** shell navigation is rendered for an authorized admin user
- **THEN** `/admin/pages` and `/admin/performance` SHALL be reachable through governed admin navigation targets
#### Scenario: Deferred routes excluded from this phase architecture criteria
- **WHEN** this phase architecture compliance is evaluated
- **THEN** `/tables`, `/excel-query`, `/query-tool`, and `/mid-section-defect` SHALL be excluded and handled in a follow-up change
### Requirement: Vite config SHALL support Vue SFC and HTML entry points
The Vite build configuration SHALL support Vue Single File Components alongside existing vanilla JS entries.
#### Scenario: Vue plugin coexistence
- **WHEN** `vite build` is executed
- **THEN** Vue SFC (`.vue` files) SHALL be compiled by `@vitejs/plugin-vue`
- **THEN** existing vanilla JS entry points SHALL continue to build without modification
#### Scenario: HTML entry point
- **WHEN** a page uses an HTML file as its Vite entry point
- **THEN** Vite SHALL process the HTML and its referenced JS/CSS into `static/dist/`
- **THEN** the output SHALL include `<page-name>.html`, `<page-name>.js`, and `<page-name>.css`
#### Scenario: Chunk splitting
- **WHEN** Vite builds the project
- **THEN** Vue runtime SHALL be split into a `vendor-vue` chunk
- **THEN** ECharts modules (including TreemapChart, BarChart, LineChart) SHALL be split into the existing `vendor-echarts` chunk
- **THEN** chunk splitting SHALL NOT affect existing page bundles
#### Scenario: Migrated page entry replacement
- **WHEN** a vanilla JS page is migrated to Vue 3
- **THEN** its Vite entry SHALL change from JS file to HTML file (e.g., `src/wip-overview/main.js``src/wip-overview/index.html`)
- **THEN** the original JS entry SHALL be replaced, not kept alongside
#### Scenario: Hold Overview entry point
- **WHEN** the hold-overview page is added
- **THEN** `vite.config.js` input SHALL include `'hold-overview': resolve(__dirname, 'src/hold-overview/index.html')`
- **THEN** the build SHALL produce `hold-overview.html`, `hold-overview.js`, and `hold-overview.css` in `static/dist/`
#### Scenario: Hold History entry point
- **WHEN** the hold-history page is added
- **THEN** `vite.config.js` input SHALL include `'hold-history': resolve(__dirname, 'src/hold-history/index.html')`
- **THEN** the build SHALL produce `hold-history.html`, `hold-history.js`, and `hold-history.css` in `static/dist/`
#### Scenario: Shared CSS import across migrated pages
- **WHEN** multiple migrated pages import a shared CSS module (e.g., `wip-shared/styles.css`)
- **THEN** Vite SHALL bundle the shared CSS into each page's output CSS
- **THEN** shared CSS SHALL NOT create a separate shared chunk that requires additional HTTP requests
#### Scenario: Shared composable import across module boundaries
- **WHEN** a migrated page imports a composable from another shared module (e.g., `hold-history` imports `useAutoRefresh` from `wip-shared/`)
- **THEN** the composable SHALL be bundled into the importing page's JS output
- **THEN** cross-module imports SHALL NOT create unexpected shared chunks
### Requirement: Pure Vite pages SHALL handle API calls without legacy MesApi
Pure Vite pages SHALL use the existing `frontend/src/core/api.js` module for API communication without depending on the global `window.MesApi` object from `_base.html`.
#### Scenario: API GET request from pure Vite page
- **WHEN** a pure Vite page makes a GET API call
- **THEN** the call SHALL use the `apiGet` function from `core/api.js`
- **THEN** the call SHALL work without `window.MesApi` being present
### Requirement: Pure Vite pages SHALL handle POST API calls without legacy MesApi
Pure Vite pages SHALL use the `apiPost` function from `core/api.js` for POST requests without depending on `window.MesApi`.
#### Scenario: API POST request from pure Vite page
- **WHEN** a pure Vite page makes a POST API call
- **THEN** the call SHALL use the `apiPost` function from `core/api.js`
- **THEN** the call SHALL include `Content-Type: application/json` header
- **THEN** the call SHALL work without `window.MesApi` being present
#### Scenario: CSRF token handling in POST requests
- **WHEN** a pure Vite page calls `apiPost`
- **THEN** `apiPost` SHALL attempt to read CSRF token from `<meta name="csrf-token">`
- **THEN** if no meta tag exists, the request SHALL still proceed (non-admin APIs do not enforce CSRF)
### Requirement: Pure Vite pages with server-side route validation SHALL use send_from_directory with pre-validation
Pages that require server-side parameter validation before serving SHALL validate parameters in the Flask route and then serve the static HTML.
#### Scenario: Hold Detail reason validation
- **WHEN** user navigates to `/hold-detail` without a `reason` parameter
- **THEN** Flask SHALL redirect to `/wip-overview`
- **WHEN** user navigates to `/hold-detail?reason={value}`
- **THEN** Flask SHALL serve the pre-built HTML file from `static/dist/` via `send_from_directory`
- **THEN** the HTML SHALL NOT pass through Jinja2 template rendering
#### Scenario: Frontend fallback validation
- **WHEN** the pure Vite hold-detail page loads
- **THEN** the page SHALL read `reason` from URL parameters
- **THEN** if `reason` is empty or missing, the page SHALL redirect to `/wip-overview`
### Requirement: Mid-section defect page SHALL separate filter state from query state
The mid-section defect page SHALL maintain separate reactive state for UI input (`filters`) and committed query parameters (`committedFilters`).
#### Scenario: User changes date without clicking query
- **WHEN** user modifies the date range in the filter bar but does not click "查詢"
- **THEN** auto-refresh, pagination, and CSV export SHALL continue using the previously committed filter values
- **THEN** the new date range SHALL NOT affect any API calls until "查詢" is clicked
#### Scenario: User clicks query button
- **WHEN** user clicks "查詢"
- **THEN** the current `filters` state SHALL be snapshotted into `committedFilters`
- **THEN** all subsequent API calls SHALL use the committed values
#### Scenario: CSV export uses committed filters
- **WHEN** user clicks "匯出 CSV" after modifying filters without re-querying
- **THEN** the export SHALL use the committed filter values from the last query
- **THEN** the export SHALL NOT use the current UI filter values
### Requirement: Mid-section defect page SHALL cancel in-flight requests on new query
The mid-section defect page SHALL use `AbortController` to cancel in-flight API requests when a new query is initiated.
#### Scenario: New query cancels previous query
- **WHEN** user clicks "查詢" while a previous query is still in-flight
- **THEN** the previous query's summary and detail requests SHALL be aborted
- **THEN** the AbortError SHALL be handled silently (no error banner shown)
#### Scenario: Page navigation cancels previous detail request
- **WHEN** user clicks next page while a previous page request is still in-flight
- **THEN** the previous page request SHALL be aborted
- **THEN** the new page request SHALL proceed independently
#### Scenario: Query and pagination use independent abort keys
- **WHEN** a query is in-flight and user triggers pagination
- **THEN** the query SHALL NOT be cancelled by the pagination request
- **THEN** the pagination SHALL use a separate abort key from the query