## 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 `.html`, `.js`, and `.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 `` - **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 ### Requirement: Reject History page SHALL be a pure Vite HTML entry The reject-history page SHALL be built from an HTML entry and emitted as static dist assets. #### Scenario: Vite entry registration - **WHEN** Vite config inputs are evaluated - **THEN** `reject-history` SHALL map to `frontend/src/reject-history/index.html` #### Scenario: Build output artifacts - **WHEN** `vite build` completes - **THEN** output SHALL include `reject-history.html`, `reject-history.js`, and `reject-history.css` in `static/dist/` ### Requirement: Reject History route SHALL serve static dist HTML The Flask route for `/reject-history` SHALL serve pre-built static HTML through `send_from_directory`. #### Scenario: Static page serving - **WHEN** user navigates to `/reject-history` - **THEN** Flask SHALL serve `static/dist/reject-history.html` when the file exists - **THEN** HTML SHALL NOT be rendered through Jinja template interpolation #### Scenario: Dist fallback response - **WHEN** `reject-history.html` is missing in dist - **THEN** route SHALL return a minimal fallback HTML that still references `/static/dist/reject-history.js` ### Requirement: Reject History shell integration SHALL use native module loading The page SHALL integrate with portal-shell native module loading policy. #### Scenario: Native module registration - **WHEN** shell resolves a route component for `/reject-history` - **THEN** it SHALL dynamically import `frontend/src/reject-history/App.vue` - **THEN** the route style bundle SHALL be loaded via registered style loaders ### Requirement: Reject History page SHALL call APIs through shared core API module The page SHALL call backend APIs via `frontend/src/core/api.js` without legacy global dependencies. #### Scenario: API call path - **WHEN** reject-history page executes GET or export requests - **THEN** requests SHALL use shared API utilities (`apiGet`/equivalent) - **THEN** page behavior SHALL NOT depend on `window.MesApi`