Files
DashBoard/openspec/specs/vue-vite-page-architecture/spec.md
egg 7cb0985b12 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>
2026-02-12 11:26:02 +08:00

8.2 KiB

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.jssrc/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