## ADDED Requirements ### Requirement: Runtime console pages SHALL be served by the Runtime FastAPI application The Runtime FastAPI application SHALL serve its operator console from same-origin `/ui/` routes without requiring a separately running frontend process, a prebuilt SPA directory, or `RUNTIME_CONSOLE_STATIC_DIR`. `GET /` SHALL redirect an operator to `/ui/`. #### Scenario: Open the built-in console without static-directory configuration - **WHEN** the Runtime application starts without `RUNTIME_CONSOLE_STATIC_DIR` - **THEN** `GET /` redirects to `/ui/` and `GET /ui/` returns an HTML dashboard rendered by the Runtime process #### Scenario: Navigate the operator workflows through page routes - **WHEN** an operator opens `/ui/tasks`, `/ui/tasks/{task_id}`, or `/ui/config` - **THEN** the Runtime returns HTML pages for task browsing, task timeline detail, and device/runtime configuration respectively ### Requirement: Runtime console HTML SHALL use one autoescaping template environment Every Runtime console HTML response SHALL be rendered through one process-wide Jinja2 environment configured to autoescape `.html` and `.xml` templates. Page handlers SHALL NOT construct HTML through f-strings, string concatenation, or post-process rendered output to bypass that environment. #### Scenario: Untrusted device and task values are rendered safely - **WHEN** a device name, task goal, failure reason, or timeline value contains `` - **THEN** the rendered console HTML contains an escaped text representation and contains no script element originating from that value #### Scenario: A future page inherits HTML autoescaping - **WHEN** a future Runtime console route renders a `.html` template through the shared environment - **THEN** its interpolated values are HTML-escaped without route-specific escaping configuration ### Requirement: Runtime console pages SHALL preserve console operational workflows The server-rendered console SHALL let an operator inspect device status, list and filter tasks, inspect a task's ordered timeline including available screenshots, register or remove a supported device, and view or update `max_steps`. Successful configuration mutations SHALL use POST/Redirect/GET; invalid form input SHALL be re-rendered as a readable HTML error without applying a partial mutation. #### Scenario: Browse filtered tasks and inspect a timeline - **WHEN** an operator selects a device or status filter and opens a known task - **THEN** the task list contains only matching tasks and the task page renders its timeline in step order with its screenshot when one exists #### Scenario: Register a device from the configuration page - **WHEN** an operator submits valid supported-device form values - **THEN** the Runtime registers and persists the device, responds with a redirect to the configuration page, and the device is visible after the redirect #### Scenario: Reject invalid configuration without partial mutation - **WHEN** an operator submits an unsupported driver type, malformed connection value, or non-positive `max_steps` - **THEN** the Runtime returns an HTML validation error, preserves the prior Runtime/configuration state, and does not perform a redirect ### Requirement: Dashboard live status SHALL remain server-rendered The dashboard SHALL retain periodic live-status refresh without restoring a client-side application framework. Its browser enhancement SHALL request a server-rendered HTML fragment and replace only the live-status region; it SHALL NOT rebuild console state from a JSON API response. #### Scenario: Refresh dashboard status after the polling interval - **WHEN** the dashboard refresh enhancement runs while the Runtime is available - **THEN** it retrieves a Jinja2-rendered status fragment and updates the dashboard's live-status region without a full-page reload ### Requirement: Existing console JSON API SHALL remain compatible The Runtime SHALL continue to expose the existing `/console/devices`, `/console/tasks`, `/console/tasks/{task_id}`, `/console/tasks/{task_id}/timeline`, and `/console/config` JSON endpoints with their existing methods, status codes, payloads, filtering behavior, and persistence semantics. Page and JSON routes SHALL use the same API-local console operations rather than issuing HTTP requests to each other. #### Scenario: Programmatic client reads console data after the UI migration - **WHEN** a client calls an existing `GET /console/*` endpoint after the server-rendered console is deployed - **THEN** it receives the same JSON response shape and status behavior as before the migration #### Scenario: A page mutation is visible through the JSON API - **WHEN** an operator registers or removes a device or updates `max_steps` through a `/ui/` form - **THEN** the corresponding `/console/*` JSON endpoint reports the same resulting Runtime state ### Requirement: Runtime console templates and assets SHALL ship with the Python package The Runtime distribution SHALL package all console templates and static assets needed by `/ui/`. The Runtime console SHALL not depend on the top-level Vue/Vite `console/` project, Node package installation, Vite configuration, or the SPA-only static-directory mount. Same-origin console operation SHALL not require wildcard CORS configured for Vite development. #### Scenario: Run the console from an installed Runtime wheel - **WHEN** `device-agent-runtime` is built and installed outside the source checkout - **THEN** the Runtime can serve `/ui/` and its required CSS/browser assets from packaged resources #### Scenario: Start a Runtime after the SPA workflow is removed - **WHEN** an operator starts the Runtime API using the documented Python command - **THEN** the console is available at `/ui/` without `npm install`, `npm run build`, `VITE_API_BASE_URL`, or `RUNTIME_CONSOLE_STATIC_DIR`