From 2ee8b2d82b3165cd89de350bbb3c7afb9110ae1e Mon Sep 17 00:00:00 2001 From: NuklearRabbit <145918611+NuklearRabbit@users.noreply.github.com> Date: Mon, 10 Aug 2026 04:02:36 +0200 Subject: [PATCH] M15: synchronize contracts and acceptance --- PROJECT_STATE.md | 42 + .../versions/d1f83bc64170_revoked_sessions.py | 49 + backend/app/api/deps.py | 4 +- backend/app/api/routers/auth.py | 5 +- backend/app/api/routers/demo.py | 5 +- backend/app/core/security.py | 6 + backend/app/models/__init__.py | 2 + backend/app/models/revoked_session.py | 16 + backend/app/seed_loader.py | 2 + backend/app/services/sessions.py | 36 + backend/scripts/generate_openapi.py | 31 + backend/tests/test_auth.py | 28 + contracts/openapi.yaml | 4377 +++++++++++++++-- docs/17-runbook.md | 21 + frontend/Dockerfile | 3 +- frontend/e2e/clickable-rows.spec.ts | 11 +- frontend/e2e/demo-legibility.spec.ts | 6 +- frontend/e2e/demo.spec.ts | 6 +- frontend/e2e/fleet-ops-correction.spec.ts | 15 +- frontend/e2e/guided-demo-full.spec.ts | 7 +- frontend/e2e/interactive-elements.spec.ts | 50 +- frontend/e2e/operational-workflows.spec.ts | 71 + frontend/e2e/roadmap-regression.spec.ts | 22 +- frontend/e2e/ui-redesign.spec.ts | 6 +- frontend/package-lock.json | 1855 ++----- frontend/package.json | 4 +- frontend/src/api/errorMessages.ts | 1 + frontend/src/components/Layout.tsx | 2 +- frontend/src/context/AuthContext.tsx | 25 +- frontend/src/i18n/locales/en-GB/errors.json | 4 + .../src/i18n/locales/en-GB/navigation.json | 6 +- frontend/src/i18n/locales/fr-BE/errors.json | 4 + .../src/i18n/locales/fr-BE/navigation.json | 6 +- .../src/i18n/locales/fr-BE/operations.json | 2 +- frontend/src/i18n/locales/nl-BE/errors.json | 4 + .../src/i18n/locales/nl-BE/operations.json | 2 +- frontend/src/pages/BookingDetail.tsx | 6 +- n8n/workflows/merge_credential_refs.py | 76 + 38 files changed, 5091 insertions(+), 1727 deletions(-) create mode 100644 backend/alembic/versions/d1f83bc64170_revoked_sessions.py create mode 100644 backend/app/models/revoked_session.py create mode 100644 backend/app/services/sessions.py create mode 100644 backend/scripts/generate_openapi.py create mode 100644 frontend/e2e/operational-workflows.spec.ts create mode 100644 n8n/workflows/merge_credential_refs.py diff --git a/PROJECT_STATE.md b/PROJECT_STATE.md index 6337434..79813aa 100644 --- a/PROJECT_STATE.md +++ b/PROJECT_STATE.md @@ -2356,3 +2356,45 @@ evidence yet." execution ID visible. - Exact next action: regenerate the checked-in OpenAPI contract, add E2E coverage for the new operator workflows, document credential-safe n8n upgrades and run acceptance. + +## Contract and acceptance synchronization (2026-08-10) + +- Replaced the obsolete hand-maintained OpenAPI baseline with a deterministic snapshot + generated directly from the FastAPI application. The committed contract now describes + all 53 paths and 75 schemas, including booking lifecycle, operational users, MCP trust + headers and n8n execution telemetry. +- Added browser acceptance coverage for booking creation/cancellation, user + creation/deactivation and vehicle maintenance/release. Existing acceptance journeys + now reset their own state, tolerate the configured honest knowledge provider and allow + the provider's bounded response window instead of depending on suite order or a demo + provider that is not active in production. +- Added a credential-reference merge utility and a runbook procedure that preserves live + n8n credential IDs during workflow upgrades without exporting or committing secrets. +- Hardened logout beyond browser cookie deletion: signed sessions now carry a unique + nonce, logout persists a token-hash denylist, expired revocations are pruned, and both + demo and operational authentication reject retained or copied cookies server-side. + Repeated live refresh/logout coverage passed **40/40**. +- Removed a return-form initialization race: the return form now mounts only after the + persisted demo manifest is ready, so operator input cannot be overwritten by a late + manifest response. The final mobile row interaction is represented by actual link + semantics rather than a nested interactive table row. +- Split frontend dependency installation from source compilation in the Docker build so + dependency layers are cached and reproducible. Upgraded the build toolchain to pinned + Vite **8.2.1** and `@vitejs/plugin-react` **6.0.5**; both the production-only and full + npm audits report **0 vulnerabilities**. +- **Final acceptance evidence**: generated OpenAPI output is byte-for-byte deterministic; + frontend TypeScript/production build, ruff, mypy and diff checks pass; Alembic reports + `d1f83bc64170 (head)`; the complete backend suite passes **213 tests**; the complete + Playwright suite passes **144 tests in 4.8 minutes** against the deployed production + bundle. +- **Live integration evidence**: a real vehicle return completed through local commit → + outbox → the existing central n8n → callback → heartbeat. After the final browser run, + Vehicle Return execution **376** is healthy, n8n is operational with zero pending + events, MCP Hub is reachable and operational with a real audited + `fleet_ops_get_operations_summary` call, and RAGcore reports available/ready. +- **Restored hand-off state**: the synthetic reset reports all scenarios ready with 2 + users, 180 customers, 50 vehicles, 254 bookings, 75 inspections, 40 maintenance + records, 33 data-quality issues and 20 workflow runs; `BK-DEMO-RETURN` is active again. +- **Exact next action**: none for the locked PoC. All acceptance criteria are satisfied; + subsequent work is routine production operation, monitoring and explicitly approved + scope beyond this build. diff --git a/backend/alembic/versions/d1f83bc64170_revoked_sessions.py b/backend/alembic/versions/d1f83bc64170_revoked_sessions.py new file mode 100644 index 0000000..9026b6c --- /dev/null +++ b/backend/alembic/versions/d1f83bc64170_revoked_sessions.py @@ -0,0 +1,49 @@ +"""persist revoked sessions + +Revision ID: d1f83bc64170 +Revises: b7c7b536df85 +""" + +import sqlalchemy as sa + +from alembic import op + +revision = "d1f83bc64170" +down_revision = "b7c7b536df85" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.create_table( + "revoked_sessions", + sa.Column("token_hash", sa.String(length=64), nullable=False), + sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False), + sa.Column("id", sa.Uuid(), nullable=False), + sa.Column( + "created_at", + sa.DateTime(timezone=True), + server_default=sa.text("now()"), + nullable=False, + ), + sa.Column( + "updated_at", + sa.DateTime(timezone=True), + server_default=sa.text("now()"), + nullable=False, + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index("ix_revoked_sessions_expires_at", "revoked_sessions", ["expires_at"]) + op.create_index( + "ix_revoked_sessions_token_hash", + "revoked_sessions", + ["token_hash"], + unique=True, + ) + + +def downgrade() -> None: + op.drop_index("ix_revoked_sessions_token_hash", table_name="revoked_sessions") + op.drop_index("ix_revoked_sessions_expires_at", table_name="revoked_sessions") + op.drop_table("revoked_sessions") diff --git a/backend/app/api/deps.py b/backend/app/api/deps.py index 2fbf7a9..7cf7656 100644 --- a/backend/app/api/deps.py +++ b/backend/app/api/deps.py @@ -13,6 +13,7 @@ from app.core.db import SessionLocal from app.core.security import SessionPayload, read_session_token from app.models.user import User from app.schemas import CurrentUser, Role +from app.services.sessions import is_session_revoked settings = get_settings() _VALID_ROLES = frozenset(Role.__args__) # type: ignore[attr-defined] @@ -29,7 +30,8 @@ def get_db() -> Generator[Session, None, None]: def get_current_user(request: Request, db: Session = Depends(get_db)) -> CurrentUser: token = request.cookies.get(settings.session_cookie_name) payload: SessionPayload | None = read_session_token(token) if token else None - if payload is None or payload.role not in _VALID_ROLES: + revoked = token is not None and is_session_revoked(db, token) + if payload is None or payload.role not in _VALID_ROLES or revoked: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated") # Demo reset deliberately rebuilds the deterministic users table. Retaining the # signed demo session until the reset endpoint clears its cookie keeps existing demo diff --git a/backend/app/api/routers/auth.py b/backend/app/api/routers/auth.py index 053dbd6..f241dcb 100644 --- a/backend/app/api/routers/auth.py +++ b/backend/app/api/routers/auth.py @@ -19,6 +19,7 @@ from app.core.security import ( from app.models.user import User from app.schemas import CurrentUser, PasswordLoginRequest from app.services.audit import record_audit_event +from app.services.sessions import revoke_session router = APIRouter(prefix="/api/v1/auth", tags=["auth"]) settings = get_settings() @@ -37,6 +38,7 @@ def _set_session(response: Response, user: User) -> None: SessionPayload( user_id=str(user.id), public_ref=user.public_ref, role=user.role, display_name=user.display_name, issued_at=int(time.time()), + session_id=str(uuid.uuid4()), ) ) response.set_cookie( @@ -114,7 +116,8 @@ def get_session(response: Response, user: CurrentUser = Depends(get_current_user def logout(request: Request, response: Response, db: Session = Depends(get_db)) -> dict: token = request.cookies.get(settings.session_cookie_name) payload = read_session_token(token) if token else None - if payload is not None: + if payload is not None and token is not None: + revoke_session(db, token, payload) record_audit_event( db, actor_type="user", diff --git a/backend/app/api/routers/demo.py b/backend/app/api/routers/demo.py index 1d3331e..d22bb11 100644 --- a/backend/app/api/routers/demo.py +++ b/backend/app/api/routers/demo.py @@ -15,6 +15,7 @@ from app.schemas import CurrentUser, DemoLoginRequest, DemoManifestOut from app.seed_loader import reset_and_seed from app.services.audit import record_audit_event from app.services.demo_manifest import build_demo_manifest, scenario_integrity_report +from app.services.sessions import revoke_session router = APIRouter(prefix="/api/v1/demo", tags=["demo"]) settings = get_settings() @@ -48,6 +49,7 @@ def demo_login( role=user.role, display_name=user.display_name, issued_at=int(time.time()), + session_id=str(uuid.uuid4()), ) ) response.set_cookie( @@ -85,7 +87,8 @@ def get_session( def demo_logout(request: Request, response: Response, db: Session = Depends(get_db)) -> dict: token = request.cookies.get(settings.session_cookie_name) payload = read_session_token(token) if token else None - if payload is not None: + if payload is not None and token is not None: + revoke_session(db, token, payload) record_audit_event( db, actor_type="user", diff --git a/backend/app/core/security.py b/backend/app/core/security.py index 15b36a6..d7751af 100644 --- a/backend/app/core/security.py +++ b/backend/app/core/security.py @@ -20,6 +20,7 @@ class SessionPayload: role: str display_name: str issued_at: int + session_id: str = "" def _sign(data: bytes) -> str: @@ -27,6 +28,11 @@ def _sign(data: bytes) -> str: return base64.urlsafe_b64encode(digest).decode().rstrip("=") +def session_token_hash(token: str) -> str: + """Return a non-reversible identifier safe to persist for token revocation.""" + return hashlib.sha256(token.encode()).hexdigest() + + def create_session_token(payload: SessionPayload) -> str: body = json.dumps(payload.__dict__, separators=(",", ":")).encode() encoded_body = base64.urlsafe_b64encode(body).decode().rstrip("=") diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py index dabc437..d6cd777 100644 --- a/backend/app/models/__init__.py +++ b/backend/app/models/__init__.py @@ -7,6 +7,7 @@ from app.models.idempotency import IdempotencyRecord from app.models.inspection import Inspection from app.models.maintenance import MaintenanceRecord from app.models.outbox import OutboxEvent +from app.models.revoked_session import RevokedSession from app.models.user import User from app.models.vehicle import Vehicle @@ -20,6 +21,7 @@ __all__ = [ "Inspection", "MaintenanceRecord", "OutboxEvent", + "RevokedSession", "User", "Vehicle", ] diff --git a/backend/app/models/revoked_session.py b/backend/app/models/revoked_session.py new file mode 100644 index 0000000..0d9be8e --- /dev/null +++ b/backend/app/models/revoked_session.py @@ -0,0 +1,16 @@ +from datetime import datetime + +from sqlalchemy import DateTime, String +from sqlalchemy.orm import Mapped, mapped_column + +from app.core.db import Base +from app.models.mixins import TimestampMixin, UUIDPrimaryKeyMixin + + +class RevokedSession(UUIDPrimaryKeyMixin, TimestampMixin, Base): + __tablename__ = "revoked_sessions" + + token_hash: Mapped[str] = mapped_column(String(64), unique=True, index=True, nullable=False) + expires_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), nullable=False, index=True + ) diff --git a/backend/app/seed_loader.py b/backend/app/seed_loader.py index cbd823d..df9945c 100644 --- a/backend/app/seed_loader.py +++ b/backend/app/seed_loader.py @@ -19,6 +19,7 @@ from app.models.idempotency import IdempotencyRecord from app.models.inspection import Inspection from app.models.maintenance import MaintenanceRecord from app.models.outbox import DEMO_SCENARIO_ERROR_CODE, OutboxEvent +from app.models.revoked_session import RevokedSession from app.models.user import User from app.models.vehicle import Vehicle from app.services.audit import record_audit_event @@ -92,6 +93,7 @@ _PERSISTENT_TELEMETRY_ACTIONS = ( def clear_all(db: Session, *, preserve_integration_telemetry: bool = False) -> None: for model in ( + RevokedSession, OutboxEvent, IdempotencyRecord, DataQualityIssue, diff --git a/backend/app/services/sessions.py b/backend/app/services/sessions.py new file mode 100644 index 0000000..002e21e --- /dev/null +++ b/backend/app/services/sessions.py @@ -0,0 +1,36 @@ +from __future__ import annotations + +from datetime import UTC, datetime + +from sqlalchemy import delete, select +from sqlalchemy.orm import Session + +from app.core.config import get_settings +from app.core.security import SessionPayload, session_token_hash +from app.models.revoked_session import RevokedSession + +settings = get_settings() + + +def is_session_revoked(db: Session, token: str) -> bool: + token_hash = session_token_hash(token) + revoked_id = db.scalar( + select(RevokedSession.id).where(RevokedSession.token_hash == token_hash) + ) + return revoked_id is not None + + +def revoke_session(db: Session, token: str, payload: SessionPayload) -> None: + db.execute(delete(RevokedSession).where(RevokedSession.expires_at < datetime.now(UTC))) + token_hash = session_token_hash(token) + existing = db.scalar(select(RevokedSession.id).where(RevokedSession.token_hash == token_hash)) + if existing is not None: + return + db.add( + RevokedSession( + token_hash=token_hash, + expires_at=datetime.fromtimestamp( + payload.issued_at + settings.session_ttl_seconds, UTC + ), + ) + ) diff --git a/backend/scripts/generate_openapi.py b/backend/scripts/generate_openapi.py new file mode 100644 index 0000000..2e89ca9 --- /dev/null +++ b/backend/scripts/generate_openapi.py @@ -0,0 +1,31 @@ +"""Generate the checked-in API contract from the FastAPI application.""" + +from __future__ import annotations + +import sys +from pathlib import Path + +import yaml + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT / "backend")) + +from app.main import app # noqa: E402 + + +def main() -> None: + schema = app.openapi() + schema["info"]["description"] = ( + "Generated contract for Fleet Ops. The visible product name is Fleet Ops; " + "MobilityOps remains the technical repository and service identifier." + ) + schema["servers"] = [{"url": "http://localhost:8128"}] + output = ROOT / "contracts" / "openapi.yaml" + output.write_text( + yaml.safe_dump(schema, sort_keys=False, allow_unicode=True, width=100), + encoding="utf-8", + ) + + +if __name__ == "__main__": + main() diff --git a/backend/tests/test_auth.py b/backend/tests/test_auth.py index 954b428..807573f 100644 --- a/backend/tests/test_auth.py +++ b/backend/tests/test_auth.py @@ -62,6 +62,34 @@ def test_logout_invalidates_session(ops_client): assert after.status_code == 401 +def test_logout_rejects_a_cookie_even_if_the_browser_retains_it(ops_client): + from app.core.config import get_settings + + cookie_name = get_settings().session_cookie_name + stolen_token = ops_client.cookies.get(cookie_name) + assert stolen_token + + logout = ops_client.post("/api/v1/auth/logout") + assert logout.status_code == 200 + + # Simulate the browser cookie race (or a copied cookie): server-side revocation is + # authoritative and must reject the original signed token independently of deletion. + ops_client.cookies.set(cookie_name, stolen_token) + assert ops_client.get("/api/v1/auth/session").status_code == 401 + + # Remove the deliberately injected hostless cookie before exercising a normal browser + # login. Otherwise httpx sends it alongside the real testserver cookie, which is not a + # state a browser can create for the same origin/path pair. + ops_client.cookies.clear() + + # A fresh login in the same second receives a distinct signed token and remains valid. + fresh_login = ops_client.post( + "/api/v1/demo/login", json={"role": "operations_manager"} + ) + assert fresh_login.status_code == 200 + assert ops_client.get("/api/v1/auth/session").status_code == 200 + + def test_logout_without_a_session_is_safe(client): response = client.post("/api/v1/demo/logout") assert response.status_code == 200 diff --git a/contracts/openapi.yaml b/contracts/openapi.yaml index 70ae0f3..fd7bace 100644 --- a/contracts/openapi.yaml +++ b/contracts/openapi.yaml @@ -2,389 +2,4128 @@ openapi: 3.1.0 info: title: Fleet Ops API version: 0.1.0 - description: >- - Contract baseline for the Fleet Ops demo. "Fleet Ops" is the visible product name; - "mobilityops" remains the technical identifier for the repository, deployment - directory, database, and internal service/health identifiers only. -servers: - - url: http://localhost:8128 + description: Generated contract for Fleet Ops. The visible product name is Fleet Ops; MobilityOps remains + the technical repository and service identifier. paths: /health: get: - operationId: health + summary: Health + operationId: health_health_get responses: '200': - description: Healthy + description: Successful Response content: application/json: schema: - $ref: '#/components/schemas/Health' + additionalProperties: + type: string + type: object + title: Response Health Health Get + /api/v1/system/status: + get: + summary: System Status + operationId: system_status_api_v1_system_status_get + responses: + '200': + description: Successful Response + content: + application/json: + schema: + additionalProperties: true + type: object + title: Response System Status Api V1 System Status Get + /api/v1/demo/manifest: + get: + tags: + - demo + summary: Demo Manifest + operationId: demo_manifest_api_v1_demo_manifest_get + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/DemoManifestOut' + /api/v1/demo/login: + post: + tags: + - demo + summary: Demo Login + operationId: demo_login_api_v1_demo_login_post + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DemoLoginRequest' + required: true + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/CurrentUser' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/demo/session: + get: + tags: + - demo + summary: Get Session + operationId: get_session_api_v1_demo_session_get + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/CurrentUser' + /api/v1/demo/logout: + post: + tags: + - demo + summary: Demo Logout + operationId: demo_logout_api_v1_demo_logout_post + responses: + '200': + description: Successful Response + content: + application/json: + schema: + additionalProperties: true + type: object + title: Response Demo Logout Api V1 Demo Logout Post + /api/v1/demo/reset: + post: + tags: + - demo + summary: Demo Reset + operationId: demo_reset_api_v1_demo_reset_post + responses: + '200': + description: Successful Response + content: + application/json: + schema: + additionalProperties: true + type: object + title: Response Demo Reset Api V1 Demo Reset Post + /api/v1/auth/login: + post: + tags: + - auth + summary: Password Login + operationId: password_login_api_v1_auth_login_post + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PasswordLoginRequest' + required: true + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/CurrentUser' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/auth/session: + get: + tags: + - auth + summary: Get Session + operationId: get_session_api_v1_auth_session_get + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/CurrentUser' + /api/v1/auth/logout: + post: + tags: + - auth + summary: Logout + operationId: logout_api_v1_auth_logout_post + responses: + '200': + description: Successful Response + content: + application/json: + schema: + additionalProperties: true + type: object + title: Response Logout Api V1 Auth Logout Post /api/v1/dashboard: get: - operationId: getDashboard + tags: + - dashboard + summary: Get Dashboard + operationId: get_dashboard_api_v1_dashboard_get responses: '200': - description: Persisted operational summary + description: Successful Response content: application/json: schema: - $ref: '#/components/schemas/Dashboard' + $ref: '#/components/schemas/DashboardOut' /api/v1/vehicles: get: - operationId: listVehicles + tags: + - vehicles + summary: List Vehicles + operationId: list_vehicles_api_v1_vehicles_get parameters: - - in: query - name: status - schema: - type: string - - in: query - name: attention_only - schema: - type: boolean - default: false - responses: - '200': - description: Vehicle list - /api/v1/vehicles/{public_ref}: - get: - operationId: getVehicle - parameters: - - $ref: '#/components/parameters/PublicRef' - responses: - '200': - description: Vehicle detail - '404': - $ref: '#/components/responses/NotFound' - /api/v1/bookings: - get: - operationId: listBookings - responses: - '200': - description: Booking list - /api/v1/bookings/{public_ref}: - get: - operationId: getBooking - parameters: - - $ref: '#/components/parameters/PublicRef' - responses: - '200': - description: Booking detail - /api/v1/bookings/{public_ref}/return-preview: - post: - operationId: previewVehicleReturn - description: >- - Non-mutating evaluation of what committing this return would do. Shares its - domain evaluation with the commit endpoint below so the two can never drift. - No writes, no audit event, no outbox event. - parameters: - - $ref: '#/components/parameters/PublicRef' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/RegisterReturnRequest' - responses: - '200': - description: Authoritative evaluation of the resulting fleet state - '409': - description: Booking is not active - /api/v1/bookings/{public_ref}/return: - post: - operationId: registerVehicleReturn - parameters: - - $ref: '#/components/parameters/PublicRef' - - in: header - name: Idempotency-Key - required: true - schema: - type: string - minLength: 8 - maxLength: 128 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/RegisterReturnRequest' - responses: - '200': - description: Existing idempotent result replayed - '201': - description: Return registered - '409': - description: State or concurrency conflict - /api/v1/data-quality/issues: - get: - operationId: listDataQualityIssues - description: Operations Manager only. - responses: - '200': - description: Quality issues - /api/v1/data-quality/scan: - post: - operationId: runDataQualityScan - description: Manual trigger for the deterministic five-rule scan. Operations Manager only. - responses: - '200': - description: Counts of newly created issues per rule type - /api/v1/data-quality/issues/{public_ref}/merge-customers: - post: - operationId: mergeDuplicateCustomers - parameters: - - $ref: '#/components/parameters/PublicRef' - responses: - '200': - description: Merge completed - '409': - description: Issue no longer mergeable - /api/v1/data-quality/issues/{public_ref}/provide-fields: - post: - operationId: provideMissingFields - description: >- - missing_required_field only. Resolves once nothing required remains missing; - otherwise leaves the issue open with updated evidence. - parameters: - - $ref: '#/components/parameters/PublicRef' - responses: - '200': - description: Issue after the update (may still be open) - '409': - description: Wrong rule type or issue not open - '422': - description: Disallowed field or empty value - /api/v1/data-quality/issues/{public_ref}/resolve-odometer-regression: - post: - operationId: resolveOdometerRegression - description: >- - odometer_regression only. Either retains the canonical odometer, or corrects a - related booking's reading -- a correction below the current canonical value is - rejected, since it would not resolve the regression. - parameters: - - $ref: '#/components/parameters/PublicRef' - responses: - '200': - description: Issue resolved - '409': - description: Wrong rule type or issue not open - '422': - description: Invalid booking reference or a correction below canonical - /api/v1/data-quality/issues/{public_ref}/resolve-overlap: - post: - operationId: resolveBookingOverlap - description: >- - booking_overlap only. Blocks one of the two overlapping bookings and - re-verifies no overlap remains before resolving. - parameters: - - $ref: '#/components/parameters/PublicRef' - responses: - '200': - description: Issue resolved - '409': - description: Wrong rule type, issue not open, or overlap still present - '422': - description: booking_ref not one of the overlapping bookings - /api/v1/data-quality/issues/{public_ref}/status-recommendation: - post: - operationId: previewVehicleStatusRecommendation - description: >- - vehicle_status_conflict only. Non-mutating: computes the recommendation from - the same shared evaluator the scanner and apply endpoint use - (app.services.vehicle_status.evaluate_vehicle_status), without resolving the - issue, writing an audit event, or queuing automation. Safe to call repeatedly - -- see docs/fleet-ops-correction/vehicle-status-decision-table.md. - parameters: - - $ref: '#/components/parameters/PublicRef' - responses: - '200': - description: >- - Current/recommended status, recommendation code, safe_to_apply, - manual_review_required, the underlying facts, and a recommendation_token - the apply endpoint revalidates against. - '409': - description: Wrong rule type, issue not open, or vehicle not found - /api/v1/data-quality/issues/{public_ref}/apply-recommended-status: - post: - operationId: applyRecommendedVehicleStatus - description: >- - vehicle_status_conflict only. Applies the one authoritative recommendation - function's output within one transaction: locks the issue and vehicle, - recomputes the recommendation from fresh facts, rejects the request if the - supplied recommendation_token no longer matches (RECOMMENDATION_STALE), refuses - an unsafe/manual-review recommendation (MANUAL_REVIEW_REQUIRED) or a - recommendation with nothing to apply (NO_CONFLICT_DETECTED), then re-validates - the same evaluator post-write before resolving the issue. - parameters: - - $ref: '#/components/parameters/PublicRef' - requestBody: - required: true - content: - application/json: - schema: - type: object - required: [recommendation_token] - properties: - recommendation_token: - type: string - description: The token from the most recent status-recommendation preview call. - responses: - '200': - description: Applied status, reason code and the resolved issue - '409': - description: >- - Wrong rule type, issue not open, vehicle not found, stale recommendation - token, manual review required, no conflict detected, or the applied status - did not resolve the conflict on re-validation - /api/v1/search: - get: - operationId: search - description: >- - Bounded typed results (vehicle, booking, data_quality_issue, section). - Data-quality and manager-only sections are filtered server-side by role. - Customers are never returned -- no customer detail route exists. Every result's - `label` is a stable public_ref/section id (never translatable prose); `detail_code` - (+ optional `detail_params` for data values like make/model/location) is what the - frontend localizes -- the backend never emits English/Dutch/French sentences here. - parameters: - - in: query - name: q - required: true - schema: - type: string + - name: status + in: query + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: Status + - name: attention_only + in: query + required: false + schema: + type: boolean + default: false + title: Attention Only + - name: query + in: query + required: false + schema: + anyOf: + - type: string minLength: 1 maxLength: 100 + - type: 'null' + title: Query + - name: page + in: query + required: false + schema: + anyOf: + - type: integer + minimum: 1 + - type: 'null' + title: Page + - name: page_size + in: query + required: false + schema: + type: integer + maximum: 25 + minimum: 1 + default: 25 + title: Page Size responses: '200': - description: Search results - /api/v1/integrations/status: + description: Successful Response + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: '#/components/schemas/VehicleOut' + - $ref: '#/components/schemas/VehiclePageOut' + title: Response List Vehicles Api V1 Vehicles Get + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/vehicles/{public_ref}: get: - operationId: getIntegrationStatus - description: >- - Truthful aggregate n8n state derived from outbox delivery counts (not just the - most recent event), plus the actual MCP Hub registration_enabled setting. - Operations Manager only. + tags: + - vehicles + summary: Get Vehicle + operationId: get_vehicle_api_v1_vehicles__public_ref__get + parameters: + - name: public_ref + in: path + required: true + schema: + type: string + title: Public Ref responses: '200': - description: n8n and MCP Hub integration status - /api/v1/integrations/n8n/scheduled-scan: + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/VehicleDetailOut' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/vehicles/{public_ref}/maintenance: post: - operationId: n8nScheduledScan - description: >- - Triggered by the scheduled n8n quality-scan workflow. Runs the same run_scan() - the manual UI action uses; idempotent by construction. - security: - - serviceToken: [] + tags: + - vehicles + summary: Create Maintenance Record + operationId: create_maintenance_record_api_v1_vehicles__public_ref__maintenance_post + parameters: + - name: public_ref + in: path + required: true + schema: + type: string + title: Public Ref + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateMaintenanceRequest' + responses: + '201': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/MaintenanceOut' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/vehicles/{public_ref}/release: + post: + tags: + - vehicles + summary: Release Vehicle + operationId: release_vehicle_api_v1_vehicles__public_ref__release_post + parameters: + - name: public_ref + in: path + required: true + schema: + type: string + title: Public Ref + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ReleaseVehicleRequest' responses: '200': - description: Counts of newly created issues per rule type - '401': - description: Invalid service token - /api/v1/knowledge/questions: + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/VehicleOut' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/bookings: + get: + tags: + - bookings + summary: List Bookings + operationId: list_bookings_api_v1_bookings_get + parameters: + - name: status + in: query + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: Status + - name: vehicle_ref + in: query + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: Vehicle Ref + - name: query + in: query + required: false + schema: + anyOf: + - type: string + minLength: 1 + maxLength: 100 + - type: 'null' + title: Query + - name: page + in: query + required: false + schema: + anyOf: + - type: integer + minimum: 1 + - type: 'null' + title: Page + - name: page_size + in: query + required: false + schema: + type: integer + maximum: 25 + minimum: 1 + default: 25 + title: Page Size + responses: + '200': + description: Successful Response + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: '#/components/schemas/BookingOut' + - $ref: '#/components/schemas/BookingPageOut' + title: Response List Bookings Api V1 Bookings Get + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' post: - operationId: askKnowledgeQuestion + tags: + - bookings + summary: Create Booking + operationId: create_booking_api_v1_bookings_post + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateBookingRequest' + responses: + '201': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/BookingOut' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/bookings/{public_ref}/checkout: + post: + tags: + - bookings + summary: Checkout Booking + operationId: checkout_booking_api_v1_bookings__public_ref__checkout_post + parameters: + - name: public_ref + in: path + required: true + schema: + type: string + title: Public Ref + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CheckoutBookingRequest' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/CheckoutBookingResult' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/bookings/availability: + get: + tags: + - bookings + summary: List Available Vehicles + operationId: list_available_vehicles_api_v1_bookings_availability_get + parameters: + - name: starts_at + in: query + required: true + schema: + type: string + format: date-time + title: Starts At + - name: ends_at + in: query + required: true + schema: + type: string + format: date-time + title: Ends At + - name: query + in: query + required: false + schema: + anyOf: + - type: string + maxLength: 100 + - type: 'null' + title: Query + - name: limit + in: query + required: false + schema: + type: integer + maximum: 50 + minimum: 1 + default: 25 + title: Limit + responses: + '200': + description: Successful Response + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/AvailableVehicleOut' + title: Response List Available Vehicles Api V1 Bookings Availability Get + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/bookings/{public_ref}: + get: + tags: + - bookings + summary: Get Booking + operationId: get_booking_api_v1_bookings__public_ref__get + parameters: + - name: public_ref + in: path + required: true + schema: + type: string + title: Public Ref + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/BookingOut' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/bookings/{public_ref}/cancel: + post: + tags: + - bookings + summary: Cancel Booking + operationId: cancel_booking_api_v1_bookings__public_ref__cancel_post + parameters: + - name: public_ref + in: path + required: true + schema: + type: string + title: Public Ref + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CancelBookingRequest' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/BookingOut' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/bookings/{public_ref}/return-preview: + post: + tags: + - bookings + summary: Preview Return + operationId: preview_return_api_v1_bookings__public_ref__return_preview_post + parameters: + - name: public_ref + in: path + required: true + schema: + type: string + title: Public Ref + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/RegisterReturnRequest' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/ReturnPreviewResult' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/bookings/{public_ref}/return: + post: + tags: + - bookings + summary: Register Return + operationId: register_return_api_v1_bookings__public_ref__return_post + parameters: + - name: public_ref + in: path + required: true + schema: + type: string + title: Public Ref + - name: Idempotency-Key + in: header + required: true + schema: + type: string + minLength: 8 + maxLength: 128 + title: Idempotency-Key + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/RegisterReturnRequest' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/RegisterReturnResult' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/customers: + get: + tags: + - customers + summary: Search Customers + operationId: search_customers_api_v1_customers_get + parameters: + - name: query + in: query + required: true + schema: + type: string + minLength: 2 + maxLength: 100 + title: Query + - name: limit + in: query + required: false + schema: + type: integer + maximum: 50 + minimum: 1 + default: 20 + title: Limit + responses: + '200': + description: Successful Response + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/CustomerOptionOut' + title: Response Search Customers Api V1 Customers Get + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/audit: + get: + tags: + - audit + summary: List Audit Events + operationId: list_audit_events_api_v1_audit_get + parameters: + - name: actor_label + in: query + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: Actor Label + - name: action + in: query + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: Action + - name: entity_type + in: query + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: Entity Type + - name: entity_ref + in: query + required: false + schema: + anyOf: + - type: string + minLength: 1 + maxLength: 100 + - type: 'null' + title: Entity Ref + - name: correlation_id + in: query + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: Correlation Id + - name: occurred_from + in: query + required: false + schema: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Occurred From + - name: occurred_to + in: query + required: false + schema: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Occurred To + - name: page + in: query + required: false + schema: + anyOf: + - type: integer + minimum: 1 + - type: 'null' + title: Page + - name: page_size + in: query + required: false + schema: + type: integer + maximum: 25 + minimum: 1 + default: 25 + title: Page Size + responses: + '200': + description: Successful Response + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: '#/components/schemas/AuditEventOut' + - $ref: '#/components/schemas/AuditEventPageOut' + title: Response List Audit Events Api V1 Audit Get + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/data-quality/issues: + get: + tags: + - data-quality + summary: List Issues + operationId: list_issues_api_v1_data_quality_issues_get + parameters: + - name: status + in: query + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: Status + - name: rule_type + in: query + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: Rule Type + - name: severity + in: query + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: Severity + - name: page + in: query + required: false + schema: + anyOf: + - type: integer + minimum: 1 + - type: 'null' + title: Page + - name: page_size + in: query + required: false + schema: + type: integer + maximum: 25 + minimum: 1 + default: 25 + title: Page Size + responses: + '200': + description: Successful Response + content: + application/json: + schema: + anyOf: + - type: array + items: + $ref: '#/components/schemas/DataQualityIssueOut' + - $ref: '#/components/schemas/DataQualityIssuePageOut' + title: Response List Issues Api V1 Data Quality Issues Get + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/data-quality/issues/{public_ref}: + get: + tags: + - data-quality + summary: Get Issue + operationId: get_issue_api_v1_data_quality_issues__public_ref__get + parameters: + - name: public_ref + in: path + required: true + schema: + type: string + title: Public Ref + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/DataQualityIssueDetailOut' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/data-quality/issues/{public_ref}/defer: + post: + tags: + - data-quality + summary: Defer + operationId: defer_api_v1_data_quality_issues__public_ref__defer_post + parameters: + - name: public_ref + in: path + required: true + schema: + type: string + title: Public Ref + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/DataQualityIssueOut' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/data-quality/issues/{public_ref}/reject: + post: + tags: + - data-quality + summary: Reject + operationId: reject_api_v1_data_quality_issues__public_ref__reject_post + parameters: + - name: public_ref + in: path + required: true + schema: + type: string + title: Public Ref + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/DataQualityIssueOut' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/data-quality/issues/{public_ref}/merge-customers: + post: + tags: + - data-quality + summary: Merge + operationId: merge_api_v1_data_quality_issues__public_ref__merge_customers_post + parameters: + - name: public_ref + in: path + required: true + schema: + type: string + title: Public Ref + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/MergeCustomersRequest' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/MergeCustomersResult' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/data-quality/issues/{public_ref}/provide-fields: + post: + tags: + - data-quality + summary: Provide Fields + operationId: provide_fields_api_v1_data_quality_issues__public_ref__provide_fields_post + parameters: + - name: public_ref + in: path + required: true + schema: + type: string + title: Public Ref + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ProvideFieldsRequest' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/DataQualityIssueOut' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/data-quality/issues/{public_ref}/resolve-odometer-regression: + post: + tags: + - data-quality + summary: Resolve Odometer + operationId: resolve_odometer_api_v1_data_quality_issues__public_ref__resolve_odometer_regression_post + parameters: + - name: public_ref + in: path + required: true + schema: + type: string + title: Public Ref + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ResolveOdometerRegressionRequest' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/DataQualityIssueOut' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/data-quality/issues/{public_ref}/resolve-overlap: + post: + tags: + - data-quality + summary: Resolve Overlap + operationId: resolve_overlap_api_v1_data_quality_issues__public_ref__resolve_overlap_post + parameters: + - name: public_ref + in: path + required: true + schema: + type: string + title: Public Ref + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ResolveOverlapRequest' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/DataQualityIssueOut' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/data-quality/issues/{public_ref}/status-recommendation: + post: + tags: + - data-quality + summary: Status Recommendation + description: 'Non-mutating preview: computes the recommendation without changing anything, + + resolving no issue and writing no audit event. Safe to call repeatedly.' + operationId: status_recommendation_api_v1_data_quality_issues__public_ref__status_recommendation_post + parameters: + - name: public_ref + in: path + required: true + schema: + type: string + title: Public Ref + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/StatusRecommendationOut' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/data-quality/issues/{public_ref}/apply-recommended-status: + post: + tags: + - data-quality + summary: Apply Status + operationId: apply_status_api_v1_data_quality_issues__public_ref__apply_recommended_status_post + parameters: + - name: public_ref + in: path + required: true + schema: + type: string + title: Public Ref + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ApplyRecommendedStatusRequest' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/ApplyRecommendedStatusResult' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/data-quality/scan: + post: + tags: + - data-quality + summary: Scan + operationId: scan_api_v1_data_quality_scan_post + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/ScanResultOut' + /api/v1/workflows: + get: + tags: + - workflows + summary: List Workflows + operationId: list_workflows_api_v1_workflows_get + parameters: + - name: status + in: query + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: Status + responses: + '200': + description: Successful Response + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/AutomationRunOut' + title: Response List Workflows Api V1 Workflows Get + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/workflows/{event_id}/retry: + post: + tags: + - workflows + summary: Retry Workflow + operationId: retry_workflow_api_v1_workflows__event_id__retry_post + parameters: + - name: event_id + in: path + required: true + schema: + type: string + title: Event Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/AutomationRunOut' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/integrations/n8n/heartbeat: + post: + tags: + - integrations + summary: Workflow Heartbeat + description: Authenticated, idempotent execution evidence from a canonical n8n workflow. + operationId: workflow_heartbeat_api_v1_integrations_n8n_heartbeat_post + parameters: + - name: X-Service-Token + in: header + required: true + schema: + type: string + title: X-Service-Token + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/N8nHeartbeatIn' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/N8nHeartbeatResult' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/integrations/n8n/return-callback: + post: + tags: + - integrations + summary: Return Callback + operationId: return_callback_api_v1_integrations_n8n_return_callback_post + parameters: + - name: Idempotency-Key + in: header + required: true + schema: + type: string + title: Idempotency-Key + - name: X-Service-Token + in: header + required: true + schema: + type: string + title: X-Service-Token requestBody: required: true content: application/json: schema: type: object - required: [question] - properties: - question: - type: string - minLength: 3 - maxLength: 1000 + additionalProperties: true + title: Body responses: '200': - description: Grounded, insufficient or unavailable answer + description: Successful Response + content: + application/json: + schema: + type: object + additionalProperties: true + title: Response Return Callback Api V1 Integrations N8N Return Callback Post + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/integrations/n8n/scheduled-scan: + post: + tags: + - integrations + summary: Scheduled Scan + description: 'Triggered by the scheduled n8n quality-scan workflow. Narrow, read-mostly, and + + safe to call repeatedly: run_scan() only ever creates an issue for a condition that + + doesn''t already have one open, so a duplicate or overlapping trigger does no + + duplicate domain work -- it just reports zero new issues for anything already known.' + operationId: scheduled_scan_api_v1_integrations_n8n_scheduled_scan_post + parameters: + - name: X-Service-Token + in: header + required: true + schema: + type: string + title: X-Service-Token + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/ScanResultOut' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/integrations/n8n/workflow-error: + post: + tags: + - integrations + summary: Workflow Error + description: 'Receives a bounded, secret-free failure report from the central n8n "Fleet Ops -- + + Workflow Error Handler" workflow, which is attached as the Error Workflow on every + + other Fleet Ops n8n workflow. Idempotent on execution_id: n8n may redeliver the same + + error report (e.g. after a timed-out response), so this must not double-record.' + operationId: workflow_error_api_v1_integrations_n8n_workflow_error_post + parameters: + - name: X-Service-Token + in: header + required: true + schema: + type: string + title: X-Service-Token + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/WorkflowErrorReportIn' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/WorkflowErrorReportResult' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/integrations/n8n/procedures: + get: + tags: + - integrations + summary: List Procedures + description: 'Read-only source list for the RAGcore Procedure Sync workflow: every procedure + + Markdown file Fleet Ops ships, across every supported language, with a stable + + per-document id (source_id) and a content hash so the caller can detect changes + + without re-fetching content it already has.' + operationId: list_procedures_api_v1_integrations_n8n_procedures_get + parameters: + - name: X-Service-Token + in: header + required: true + schema: + type: string + title: X-Service-Token + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/ProcedureListOut' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/integrations/n8n/procedures-sync-result: + post: + tags: + - integrations + summary: Procedures Sync Result + description: 'Receives a summary (counts only, no document content) from the n8n "Fleet Ops -- + + RAGcore Procedure Sync" workflow once it finishes uploading procedures to RAGcore. + + Idempotent on execution_id, matching the workflow-error and return-callback pattern.' + operationId: procedures_sync_result_api_v1_integrations_n8n_procedures_sync_result_post + parameters: + - name: X-Service-Token + in: header + required: true + schema: + type: string + title: X-Service-Token + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ProcedureSyncResultIn' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/ProcedureSyncResultResult' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/knowledge/questions: + post: + tags: + - knowledge + summary: Ask Question + operationId: ask_question_api_v1_knowledge_questions_post + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AskQuestionRequest' + required: true + responses: + '200': + description: Successful Response content: application/json: schema: $ref: '#/components/schemas/GroundedAnswer' - /api/v1/integrations/mcp/operations-summary: + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/knowledge/status: get: - operationId: mcpOperationsSummary - security: - - serviceToken: [] + tags: + - knowledge + summary: Knowledge Status + operationId: knowledge_status_api_v1_knowledge_status_get + parameters: + - name: language + in: query + required: false + schema: + enum: + - nl-BE + - en-GB + - fr-BE + type: string + default: en-GB + title: Language responses: '200': - description: Read-only operational summary -components: - securitySchemes: - serviceToken: - type: http - scheme: bearer - parameters: - PublicRef: - in: path - name: public_ref - required: true - schema: - type: string - responses: - NotFound: - description: Not found - schemas: - Health: - type: object - required: [status, service] - properties: - status: - const: ok - service: - const: mobilityops-api - Dashboard: - type: object - required: [metrics, attention_items] - properties: - metrics: - type: object - additionalProperties: - type: integer - attention_items: - type: array - items: - type: object - RegisterReturnRequest: - type: object - required: [end_odometer_km, fuel_level_percent, cleanliness_ok, damage_reported, technical_warning] - properties: - end_odometer_km: + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/KnowledgeHealth' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/integrations/mcp/operations-summary: + get: + tags: + - mcp + summary: Operations Summary + operationId: operations_summary_api_v1_integrations_mcp_operations_summary_get + parameters: + - name: X-Service-Token + in: header + required: true + schema: + type: string + title: X-Service-Token + - name: X-Client-Id + in: header + required: true + schema: + type: string + minLength: 1 + maxLength: 180 + title: X-Client-Id + - name: X-Tenant-Id + in: header + required: false + schema: + anyOf: + - type: string + maxLength: 120 + - type: 'null' + title: X-Tenant-Id + - name: X-Correlation-Id + in: header + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: X-Correlation-Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/OperationsSummaryOut' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/integrations/mcp/attention-vehicles: + get: + tags: + - mcp + summary: Attention Vehicles + operationId: attention_vehicles_api_v1_integrations_mcp_attention_vehicles_get + parameters: + - name: minimum_severity + in: query + required: false + schema: + type: string + pattern: ^(low|medium|high)$ + default: medium + title: Minimum Severity + - name: date + in: query + required: false + schema: + anyOf: + - type: string + format: date + - type: 'null' + title: Date + - name: limit + in: query + required: false + schema: type: integer - minimum: 0 + maximum: 50 + minimum: 1 + default: 20 + title: Limit + - name: X-Service-Token + in: header + required: true + schema: + type: string + title: X-Service-Token + - name: X-Client-Id + in: header + required: true + schema: + type: string + minLength: 1 + maxLength: 180 + title: X-Client-Id + - name: X-Tenant-Id + in: header + required: false + schema: + anyOf: + - type: string + maxLength: 120 + - type: 'null' + title: X-Tenant-Id + - name: X-Correlation-Id + in: header + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: X-Correlation-Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/AttentionVehicleOut' + title: Response Attention Vehicles Api V1 Integrations Mcp Attention Vehicles Get + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/integrations/mcp/vehicles/{vehicle_ref}: + get: + tags: + - mcp + summary: Vehicle Details + operationId: vehicle_details_api_v1_integrations_mcp_vehicles__vehicle_ref__get + parameters: + - name: vehicle_ref + in: path + required: true + schema: + type: string + title: Vehicle Ref + - name: X-Service-Token + in: header + required: true + schema: + type: string + title: X-Service-Token + - name: X-Client-Id + in: header + required: true + schema: + type: string + minLength: 1 + maxLength: 180 + title: X-Client-Id + - name: X-Tenant-Id + in: header + required: false + schema: + anyOf: + - type: string + maxLength: 120 + - type: 'null' + title: X-Tenant-Id + - name: X-Correlation-Id + in: header + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: X-Correlation-Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/McpVehicleDetailOut' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/integrations/mcp/search-knowledge: + post: + tags: + - mcp + summary: Search Knowledge + operationId: search_knowledge_api_v1_integrations_mcp_search_knowledge_post + parameters: + - name: X-Service-Token + in: header + required: true + schema: + type: string + title: X-Service-Token + - name: X-Client-Id + in: header + required: true + schema: + type: string + minLength: 1 + maxLength: 180 + title: X-Client-Id + - name: X-Tenant-Id + in: header + required: false + schema: + anyOf: + - type: string + maxLength: 120 + - type: 'null' + title: X-Tenant-Id + - name: X-Correlation-Id + in: header + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: X-Correlation-Id + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/McpKnowledgeSearchRequest' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/GroundedAnswer' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/search: + get: + tags: + - search + summary: Search + operationId: search_api_v1_search_get + parameters: + - name: q + in: query + required: true + schema: + type: string + minLength: 1 + maxLength: 100 + title: Q + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/SearchResponse' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/integrations/status: + get: + tags: + - integrations + summary: Integration Status + operationId: integration_status_api_v1_integrations_status_get + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/IntegrationStatusOut' + /api/v1/users: + get: + tags: + - users + summary: List Users + operationId: list_users_api_v1_users_get + responses: + '200': + description: Successful Response + content: + application/json: + schema: + items: + $ref: '#/components/schemas/UserOut' + type: array + title: Response List Users Api V1 Users Get + post: + tags: + - users + summary: Create User + operationId: create_user_api_v1_users_post + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CreateUserRequest' + required: true + responses: + '201': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/UserOut' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' + /api/v1/users/{public_ref}: + patch: + tags: + - users + summary: Update User + operationId: update_user_api_v1_users__public_ref__patch + parameters: + - name: public_ref + in: path + required: true + schema: + type: string + title: Public Ref + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateUserRequest' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/UserOut' + '422': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPValidationError' +components: + schemas: + ApplyRecommendedStatusRequest: + properties: + recommendation_token: + type: string + title: Recommendation Token + type: object + required: + - recommendation_token + title: ApplyRecommendedStatusRequest + ApplyRecommendedStatusResult: + properties: + issue: + $ref: '#/components/schemas/DataQualityIssueOut' + applied_status: + type: string + title: Applied Status + reason_code: + type: string + title: Reason Code + type: object + required: + - issue + - applied_status + - reason_code + title: ApplyRecommendedStatusResult + AskQuestionRequest: + properties: + question: + type: string + maxLength: 1000 + minLength: 3 + title: Question + language: + type: string + enum: + - nl-BE + - en-GB + - fr-BE + title: Language + default: en-GB + type: object + required: + - question + title: AskQuestionRequest + AttentionItem: + properties: + kind: + type: string + enum: + - quality_issue + - vehicle + title: Kind + severity: + type: string + title: Severity + rule_type: + type: string + title: Rule Type + evidence_signals: + items: + $ref: '#/components/schemas/EvidenceSignalOut' + type: array + title: Evidence Signals + link_type: + type: string + enum: + - vehicle + - booking + - customer + title: Link Type + link_ref: + type: string + title: Link Ref + issue_ref: + anyOf: + - type: string + - type: 'null' + title: Issue Ref + type: object + required: + - kind + - severity + - rule_type + - link_type + - link_ref + title: AttentionItem + AttentionVehicleOut: + properties: + vehicle_ref: + type: string + title: Vehicle Ref + severity: + type: string + enum: + - low + - medium + - high + title: Severity + rule_type: + type: string + title: Rule Type + summary: + type: string + title: Summary + detected_at: + type: string + format: date-time + title: Detected At + type: object + required: + - vehicle_ref + - severity + - rule_type + - summary + - detected_at + title: AttentionVehicleOut + AuditEventOut: + properties: + id: + type: string + title: Id + actor_type: + type: string + title: Actor Type + actor_label: + type: string + title: Actor Label + action: + type: string + title: Action + entity_type: + type: string + title: Entity Type + entity_id: + anyOf: + - type: string + - type: 'null' + title: Entity Id + entity_ref: + anyOf: + - type: string + - type: 'null' + title: Entity Ref + entity_link: + anyOf: + - type: string + - type: 'null' + title: Entity Link + correlation_id: + type: string + title: Correlation Id + occurred_at: + type: string + format: date-time + title: Occurred At + before: + anyOf: + - additionalProperties: true + type: object + - type: 'null' + title: Before + after: + anyOf: + - additionalProperties: true + type: object + - type: 'null' + title: After + metadata: + anyOf: + - additionalProperties: true + type: object + - type: 'null' + title: Metadata + type: object + required: + - id + - actor_type + - actor_label + - action + - entity_type + - entity_id + - correlation_id + - occurred_at + title: AuditEventOut + AuditEventPageOut: + properties: + items: + items: + $ref: '#/components/schemas/AuditEventOut' + type: array + title: Items + page: + type: integer + title: Page + page_size: + type: integer + title: Page Size + total: + type: integer + title: Total + total_pages: + type: integer + title: Total Pages + type: object + required: + - items + - page + - page_size + - total + - total_pages + title: AuditEventPageOut + AutomationRunOut: + properties: + event_id: + type: string + title: Event Id + event_type: + type: string + title: Event Type + aggregate_ref: + type: string + title: Aggregate Ref + status: + type: string + title: Status + attempts: + type: integer + title: Attempts + last_error: + anyOf: + - type: string + - type: 'null' + title: Last Error + last_error_code: + anyOf: + - type: string + - type: 'null' + title: Last Error Code + is_demo_scenario: + type: boolean + title: Is Demo Scenario + default: false + occurred_at: + type: string + format: date-time + title: Occurred At + type: object + required: + - event_id + - event_type + - aggregate_ref + - status + - attempts + - last_error + - last_error_code + - occurred_at + title: AutomationRunOut + AvailableVehicleOut: + properties: + public_ref: + type: string + title: Public Ref + make: + type: string + title: Make + model: + type: string + title: Model + registration_number: + type: string + title: Registration Number + location: + type: string + title: Location + operational_status: + type: string + title: Operational Status + type: object + required: + - public_ref + - make + - model + - registration_number + - location + - operational_status + title: AvailableVehicleOut + BookingOut: + properties: + public_ref: + type: string + title: Public Ref + customer_ref: + type: string + title: Customer Ref + vehicle_ref: + type: string + title: Vehicle Ref + starts_at: + type: string + format: date-time + title: Starts At + ends_at: + type: string + format: date-time + title: Ends At + status: + type: string + title: Status + start_odometer_km: + anyOf: + - type: integer + - type: 'null' + title: Start Odometer Km + end_odometer_km: + anyOf: + - type: integer + - type: 'null' + title: End Odometer Km + requirements_complete: + type: boolean + title: Requirements Complete + customer_name: + type: string + title: Customer Name + type: object + required: + - public_ref + - customer_ref + - vehicle_ref + - starts_at + - ends_at + - status + - start_odometer_km + - end_odometer_km + - requirements_complete + - customer_name + title: BookingOut + BookingPageOut: + properties: + items: + items: + $ref: '#/components/schemas/BookingOut' + type: array + title: Items + page: + type: integer + title: Page + page_size: + type: integer + title: Page Size + total: + type: integer + title: Total + total_pages: + type: integer + title: Total Pages + type: object + required: + - items + - page + - page_size + - total + - total_pages + title: BookingPageOut + BookingSummaryOut: + properties: + public_ref: + type: string + title: Public Ref + customer_ref: + type: string + title: Customer Ref + vehicle_ref: + type: string + title: Vehicle Ref + starts_at: + type: string + format: date-time + title: Starts At + ends_at: + type: string + format: date-time + title: Ends At + status: + type: string + title: Status + type: object + required: + - public_ref + - customer_ref + - vehicle_ref + - starts_at + - ends_at + - status + title: BookingSummaryOut + CancelBookingRequest: + properties: + reason: + type: string + maxLength: 500 + minLength: 3 + title: Reason + type: object + required: + - reason + title: CancelBookingRequest + CheckoutBookingRequest: + properties: + start_odometer_km: + type: integer + minimum: 0.0 + title: Start Odometer Km fuel_level_percent: type: integer - minimum: 0 - maximum: 100 + maximum: 100.0 + minimum: 0.0 + title: Fuel Level Percent cleanliness_ok: type: boolean + title: Cleanliness Ok damage_reported: type: boolean + title: Damage Reported + default: false technical_warning: type: boolean + title: Technical Warning + default: false notes: + anyOf: + - type: string + maxLength: 2000 + - type: 'null' + title: Notes + type: object + required: + - start_odometer_km + - fuel_level_percent + - cleanliness_ok + title: CheckoutBookingRequest + CheckoutBookingResult: + properties: + booking_ref: + type: string + title: Booking Ref + vehicle_ref: + type: string + title: Vehicle Ref + inspection_ref: + type: string + title: Inspection Ref + booking_status: + type: string + title: Booking Status + resulting_vehicle_status: + type: string + title: Resulting Vehicle Status + activated: + type: boolean + title: Activated + attention_reasons: + items: + type: string + type: array + title: Attention Reasons + type: object + required: + - booking_ref + - vehicle_ref + - inspection_ref + - booking_status + - resulting_vehicle_status + - activated + - attention_reasons + title: CheckoutBookingResult + CreateBookingRequest: + properties: + customer_ref: + type: string + maxLength: 20 + minLength: 3 + title: Customer Ref + vehicle_ref: + type: string + maxLength: 20 + minLength: 3 + title: Vehicle Ref + starts_at: + type: string + format: date-time + title: Starts At + ends_at: + type: string + format: date-time + title: Ends At + requirements_complete: + type: boolean + title: Requirements Complete + default: true + type: object + required: + - customer_ref + - vehicle_ref + - starts_at + - ends_at + title: CreateBookingRequest + CreateMaintenanceRequest: + properties: + occurred_at: + type: string + format: date-time + title: Occurred At + odometer_km: + type: integer + minimum: 0.0 + title: Odometer Km + category: + type: string + enum: + - periodic_service + - repair + - inspection + - tyres + - other + title: Category + summary: type: string maxLength: 2000 - GroundedAnswer: + minLength: 3 + title: Summary + next_service_km: + anyOf: + - type: integer + minimum: 0.0 + - type: 'null' + title: Next Service Km + mark_maintenance: + type: boolean + title: Mark Maintenance + default: true type: object - required: [answer, evidence_state, sources, provider, correlation_id] + required: + - occurred_at + - odometer_km + - category + - summary + title: CreateMaintenanceRequest + CreateUserRequest: + properties: + email: + type: string + maxLength: 320 + minLength: 3 + title: Email + display_name: + type: string + maxLength: 120 + minLength: 2 + title: Display Name + role: + type: string + enum: + - operations_manager + - rental_employee + title: Role + password: + type: string + maxLength: 256 + minLength: 8 + title: Password + type: object + required: + - email + - display_name + - role + - password + title: CreateUserRequest + CurrentUser: + properties: + public_ref: + type: string + title: Public Ref + display_name: + type: string + title: Display Name + role: + type: string + enum: + - operations_manager + - rental_employee + title: Role + type: object + required: + - public_ref + - display_name + - role + title: CurrentUser + CustomerOptionOut: + properties: + public_ref: + type: string + title: Public Ref + display_name: + type: string + title: Display Name + email: + anyOf: + - type: string + - type: 'null' + title: Email + type: object + required: + - public_ref + - display_name + - email + title: CustomerOptionOut + DashboardMetrics: + properties: + available: + type: integer + title: Available + rented: + type: integer + title: Rented + cleaning: + type: integer + title: Cleaning + maintenance: + type: integer + title: Maintenance + blocked: + type: integer + title: Blocked + open_quality_issues: + type: integer + title: Open Quality Issues + pending_or_failed_workflows: + type: integer + title: Pending Or Failed Workflows + type: object + required: + - available + - rented + - cleaning + - maintenance + - blocked + - open_quality_issues + - pending_or_failed_workflows + title: DashboardMetrics + DashboardOut: + properties: + metrics: + $ref: '#/components/schemas/DashboardMetrics' + attention_items: + items: + $ref: '#/components/schemas/AttentionItem' + type: array + title: Attention Items + today: + items: + $ref: '#/components/schemas/TodayItem' + type: array + title: Today + recent_automation: + items: + $ref: '#/components/schemas/AutomationRunOut' + type: array + title: Recent Automation + type: object + required: + - metrics + - attention_items + - today + - recent_automation + title: DashboardOut + DataQualityIssueDetailOut: + properties: + public_ref: + type: string + title: Public Ref + rule_type: + type: string + title: Rule Type + entity_type: + type: string + title: Entity Type + entity_ref: + type: string + title: Entity Ref + severity: + type: string + title: Severity + status: + type: string + title: Status + evidence: + additionalProperties: true + type: object + title: Evidence + detected_at: + type: string + format: date-time + title: Detected At + resolved_at: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Resolved At + entity_snapshot: + anyOf: + - additionalProperties: true + type: object + - type: 'null' + title: Entity Snapshot + related_snapshots: + items: + additionalProperties: true + type: object + type: array + title: Related Snapshots + type: object + required: + - public_ref + - rule_type + - entity_type + - entity_ref + - severity + - status + - evidence + - detected_at + title: DataQualityIssueDetailOut + DataQualityIssueOut: + properties: + public_ref: + type: string + title: Public Ref + rule_type: + type: string + title: Rule Type + entity_type: + type: string + title: Entity Type + entity_ref: + type: string + title: Entity Ref + severity: + type: string + title: Severity + status: + type: string + title: Status + evidence: + additionalProperties: true + type: object + title: Evidence + detected_at: + type: string + format: date-time + title: Detected At + resolved_at: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Resolved At + type: object + required: + - public_ref + - rule_type + - entity_type + - entity_ref + - severity + - status + - evidence + - detected_at + title: DataQualityIssueOut + DataQualityIssuePageOut: + properties: + items: + items: + $ref: '#/components/schemas/DataQualityIssueOut' + type: array + title: Items + page: + type: integer + title: Page + page_size: + type: integer + title: Page Size + total: + type: integer + title: Total + total_pages: + type: integer + title: Total Pages + type: object + required: + - items + - page + - page_size + - total + - total_pages + title: DataQualityIssuePageOut + DemoIntegrationSummaryOut: + properties: + key: + type: string + enum: + - n8n + - ragcore + - mcp_hub + title: Key + status_code: + type: string + title: Status Code + detail_code: + type: string + title: Detail Code + detail_params: + additionalProperties: + anyOf: + - type: string + - type: integer + type: object + title: Detail Params + default: {} + type: object + required: + - key + - status_code + - detail_code + title: DemoIntegrationSummaryOut + DemoLoginRequest: + properties: + role: + type: string + enum: + - operations_manager + - rental_employee + title: Role + type: object + required: + - role + title: DemoLoginRequest + DemoManifestOut: + properties: + demo_mode: + type: boolean + title: Demo Mode + organization_name: + type: string + title: Organization Name + timezone: + type: string + title: Timezone + synthetic_data: + type: boolean + title: Synthetic Data + allow_reset: + type: boolean + title: Allow Reset + last_reset_at: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Last Reset At + anchor_date: + anyOf: + - type: string + - type: 'null' + title: Anchor Date + guide_available: + type: boolean + title: Guide Available + required_roles: + items: + type: string + enum: + - operations_manager + - rental_employee + type: array + title: Required Roles + scenarios: + items: + $ref: '#/components/schemas/DemoScenarioOut' + type: array + title: Scenarios + integrations: + items: + $ref: '#/components/schemas/DemoIntegrationSummaryOut' + type: array + title: Integrations + type: object + required: + - demo_mode + - organization_name + - timezone + - synthetic_data + - allow_reset + - last_reset_at + - anchor_date + - guide_available + - required_roles + - scenarios + - integrations + title: DemoManifestOut + DemoScenarioOut: + properties: + id: + type: string + title: Id + estimated_minutes: + type: integer + title: Estimated Minutes + required_roles: + items: + type: string + enum: + - operations_manager + - rental_employee + type: array + title: Required Roles + start_path: + type: string + title: Start Path + ready: + type: boolean + title: Ready + blocked_reason_code: + anyOf: + - type: string + - type: 'null' + title: Blocked Reason Code + blocked_reason_params: + additionalProperties: + type: string + type: object + title: Blocked Reason Params + default: {} + type: object + required: + - id + - estimated_minutes + - required_roles + - start_path + - ready + title: DemoScenarioOut + EvidenceSignalOut: + properties: + code: + type: string + title: Code + params: + additionalProperties: true + type: object + title: Params + type: object + required: + - code + title: EvidenceSignalOut + GroundedAnswer: properties: answer: type: string + title: Answer evidence_state: - enum: [grounded, insufficient, unavailable] + type: string + enum: + - grounded + - insufficient + - unavailable + title: Evidence State sources: - type: array items: - type: object - required: [document_id, title, version, section, excerpt] - properties: - document_id: {type: string} - title: {type: string} - version: {type: string} - section: {type: string} - excerpt: {type: string} + $ref: '#/components/schemas/SourceCard' + type: array + title: Sources provider: type: string + title: Provider correlation_id: type: string + title: Correlation Id + type: object + required: + - answer + - evidence_state + - sources + - provider + - correlation_id + title: GroundedAnswer + HTTPValidationError: + properties: + detail: + items: + $ref: '#/components/schemas/ValidationError' + type: array + title: Detail + type: object + title: HTTPValidationError + InspectionOut: + properties: + public_ref: + type: string + title: Public Ref + booking_ref: + type: string + title: Booking Ref + type: + type: string + title: Type + fuel_level_percent: + type: integer + title: Fuel Level Percent + cleanliness_ok: + type: boolean + title: Cleanliness Ok + damage_reported: + type: boolean + title: Damage Reported + technical_warning: + type: boolean + title: Technical Warning + odometer_km: + type: integer + title: Odometer Km + completed_at: + type: string + format: date-time + title: Completed At + type: object + required: + - public_ref + - booking_ref + - type + - fuel_level_percent + - cleanliness_ok + - damage_reported + - technical_warning + - odometer_km + - completed_at + title: InspectionOut + IntegrationStatusOut: + properties: + n8n: + $ref: '#/components/schemas/N8nIntegrationStatus' + mcp_hub: + $ref: '#/components/schemas/McpHubIntegrationStatus' + type: object + required: + - n8n + - mcp_hub + title: IntegrationStatusOut + KnowledgeHealth: + properties: + provider: + type: string + title: Provider + available: + type: boolean + title: Available + detail: + type: string + title: Detail + tenant: + type: string + title: Tenant + workspace: + type: string + title: Workspace + collection: + type: string + title: Collection + document_count: + anyOf: + - type: integer + - type: 'null' + title: Document Count + type: object + required: + - provider + - available + - detail + - tenant + - workspace + - collection + - document_count + title: KnowledgeHealth + MaintenanceOut: + properties: + public_ref: + type: string + title: Public Ref + occurred_at: + type: string + format: date-time + title: Occurred At + odometer_km: + type: integer + title: Odometer Km + category: + type: string + title: Category + summary: + type: string + title: Summary + type: object + required: + - public_ref + - occurred_at + - odometer_km + - category + - summary + title: MaintenanceOut + McpHubIntegrationStatus: + properties: + registration_enabled: + type: boolean + title: Registration Enabled + state: + type: string + enum: + - not_configured + - no_evidence + - operational + title: State + total_calls: + type: integer + title: Total Calls + last_tool: + anyOf: + - type: string + - type: 'null' + title: Last Tool + last_client: + anyOf: + - type: string + - type: 'null' + title: Last Client + last_called_at: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Last Called At + hub_reachable: + anyOf: + - type: boolean + - type: 'null' + title: Hub Reachable + type: object + required: + - registration_enabled + - state + - total_calls + title: McpHubIntegrationStatus + McpKnowledgeSearchRequest: + properties: + question: + type: string + maxLength: 1000 + minLength: 3 + title: Question + max_sources: + type: integer + maximum: 8.0 + minimum: 1.0 + title: Max Sources + default: 4 + locale: + type: string + enum: + - nl-BE + - en-GB + - fr-BE + title: Locale + default: en-GB + type: object + required: + - question + title: McpKnowledgeSearchRequest + McpVehicleDetailOut: + properties: + public_ref: + type: string + title: Public Ref + make: + type: string + title: Make + model: + type: string + title: Model + model_year: + type: integer + title: Model Year + location: + type: string + title: Location + operational_status: + type: string + title: Operational Status + odometer_km: + type: integer + title: Odometer Km + next_service_km: + type: integer + title: Next Service Km + open_quality_issue_count: + type: integer + title: Open Quality Issue Count + current_booking_ref: + anyOf: + - type: string + - type: 'null' + title: Current Booking Ref + type: object + required: + - public_ref + - make + - model + - model_year + - location + - operational_status + - odometer_km + - next_service_km + - open_quality_issue_count + - current_booking_ref + title: McpVehicleDetailOut + MergeCustomersRequest: + properties: + survivor_ref: + type: string + title: Survivor Ref + field_overrides: + anyOf: + - additionalProperties: + type: string + type: object + - type: 'null' + title: Field Overrides + type: object + required: + - survivor_ref + title: MergeCustomersRequest + MergeCustomersResult: + properties: + issue_ref: + type: string + title: Issue Ref + survivor_ref: + type: string + title: Survivor Ref + loser_ref: + type: string + title: Loser Ref + rewired_bookings: + type: integer + title: Rewired Bookings + type: object + required: + - issue_ref + - survivor_ref + - loser_ref + - rewired_bookings + title: MergeCustomersResult + N8nErrorHandlerStatus: + properties: + total_failures_registered: + type: integer + title: Total Failures Registered + latest_failure_at: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Latest Failure At + latest_failure_workflow: + anyOf: + - type: string + - type: 'null' + title: Latest Failure Workflow + type: object + required: + - total_failures_registered + - latest_failure_at + - latest_failure_workflow + title: N8nErrorHandlerStatus + N8nHeartbeatIn: + properties: + workflow_id: + type: string + maxLength: 120 + minLength: 1 + title: Workflow Id + workflow_name: + type: string + maxLength: 200 + minLength: 1 + title: Workflow Name + execution_id: + type: string + maxLength: 120 + minLength: 1 + title: Execution Id + status: + type: string + enum: + - succeeded + - failed + title: Status + type: object + required: + - workflow_id + - workflow_name + - execution_id + - status + title: N8nHeartbeatIn + N8nHeartbeatResult: + properties: + status: + type: string + enum: + - registered + - already_registered + title: Status + execution_id: + type: string + title: Execution Id + occurred_at: + type: string + format: date-time + title: Occurred At + type: object + required: + - status + - execution_id + - occurred_at + title: N8nHeartbeatResult + N8nIntegrationStatus: + properties: + configured: + type: boolean + title: Configured + dispatch_enabled: + type: boolean + title: Dispatch Enabled + state: + type: string + enum: + - disabled + - unavailable + - degraded + - operational + - no_evidence + title: State + pending: + type: integer + title: Pending + failed: + type: integer + title: Failed + unexpected_failed: + type: integer + title: Unexpected Failed + default: 0 + demo_scenario_failed: + type: integer + title: Demo Scenario Failed + default: 0 + delivering: + type: integer + title: Delivering + succeeded: + type: integer + title: Succeeded + latest_success_at: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Latest Success At + latest_failure_at: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Latest Failure At + latest_demo_scenario_at: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Latest Demo Scenario At + expected_workflow_count: + type: integer + title: Expected Workflow Count + known_workflow_count: + type: integer + title: Known Workflow Count + workflows: + items: + $ref: '#/components/schemas/N8nWorkflowEvidence' + type: array + title: Workflows + error_handler: + $ref: '#/components/schemas/N8nErrorHandlerStatus' + type: object + required: + - configured + - dispatch_enabled + - state + - pending + - failed + - delivering + - succeeded + - latest_success_at + - latest_failure_at + - expected_workflow_count + - known_workflow_count + - workflows + - error_handler + title: N8nIntegrationStatus + N8nWorkflowEvidence: + properties: + name: + type: string + title: Name + built: + type: boolean + title: Built + last_seen_at: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Last Seen At + state: + type: string + enum: + - no_evidence + - healthy + - stale + - failed + title: State + default: no_evidence + last_status: + anyOf: + - type: string + enum: + - succeeded + - failed + - type: 'null' + title: Last Status + last_execution_id: + anyOf: + - type: string + - type: 'null' + title: Last Execution Id + type: object + required: + - name + - built + - last_seen_at + title: N8nWorkflowEvidence + NextBookingRisk: + properties: + booking_ref: + type: string + title: Booking Ref + starts_at: + type: string + format: date-time + title: Starts At + at_risk: + type: boolean + title: At Risk + type: object + required: + - booking_ref + - starts_at + - at_risk + title: NextBookingRisk + OperationsSummaryOut: + properties: + tenant: + type: string + title: Tenant + metrics: + $ref: '#/components/schemas/DashboardMetrics' + type: object + required: + - tenant + - metrics + title: OperationsSummaryOut + PasswordLoginRequest: + properties: + email: + type: string + maxLength: 320 + minLength: 3 + title: Email + password: + type: string + maxLength: 256 + minLength: 8 + title: Password + type: object + required: + - email + - password + title: PasswordLoginRequest + ProcedureDocumentOut: + properties: + id: + type: string + title: Id + language: + type: string + title: Language + document_id: + type: string + title: Document Id + title: + type: string + title: Title + version: + type: string + title: Version + content: + type: string + title: Content + content_hash: + type: string + title: Content Hash + type: object + required: + - id + - language + - document_id + - title + - version + - content + - content_hash + title: ProcedureDocumentOut + ProcedureListOut: + properties: + documents: + items: + $ref: '#/components/schemas/ProcedureDocumentOut' + type: array + title: Documents + type: object + required: + - documents + title: ProcedureListOut + ProcedureSyncResultIn: + properties: + execution_id: + type: string + maxLength: 120 + title: Execution Id + synced: + type: integer + minimum: 0.0 + title: Synced + failed: + type: integer + minimum: 0.0 + title: Failed + default: 0 + type: object + required: + - execution_id + - synced + title: ProcedureSyncResultIn + ProcedureSyncResultResult: + properties: + status: + type: string + enum: + - registered + - already_registered + title: Status + execution_id: + type: string + title: Execution Id + occurred_at: + type: string + format: date-time + title: Occurred At + type: object + required: + - status + - execution_id + - occurred_at + title: ProcedureSyncResultResult + ProvideFieldsRequest: + properties: + fields: + additionalProperties: + type: string + type: object + title: Fields + type: object + required: + - fields + title: ProvideFieldsRequest + RegisterReturnRequest: + properties: + end_odometer_km: + type: integer + minimum: 0.0 + title: End Odometer Km + fuel_level_percent: + type: integer + maximum: 100.0 + minimum: 0.0 + title: Fuel Level Percent + cleanliness_ok: + type: boolean + title: Cleanliness Ok + damage_reported: + type: boolean + title: Damage Reported + technical_warning: + type: boolean + title: Technical Warning + notes: + anyOf: + - type: string + maxLength: 2000 + - type: 'null' + title: Notes + type: object + required: + - end_odometer_km + - fuel_level_percent + - cleanliness_ok + - damage_reported + - technical_warning + title: RegisterReturnRequest + RegisterReturnResult: + properties: + booking_ref: + type: string + title: Booking Ref + vehicle_ref: + type: string + title: Vehicle Ref + inspection_ref: + type: string + title: Inspection Ref + resulting_vehicle_status: + type: string + title: Resulting Vehicle Status + odometer_regression: + type: boolean + title: Odometer Regression + quality_issue_ref: + anyOf: + - type: string + - type: 'null' + title: Quality Issue Ref + workflow_event_id: + type: string + title: Workflow Event Id + next_booking_risk: + anyOf: + - $ref: '#/components/schemas/NextBookingRisk' + - type: 'null' + type: object + required: + - booking_ref + - vehicle_ref + - inspection_ref + - resulting_vehicle_status + - odometer_regression + - quality_issue_ref + - workflow_event_id + - next_booking_risk + title: RegisterReturnResult + ReleaseVehicleRequest: + properties: + reason: + type: string + maxLength: 500 + minLength: 3 + title: Reason + type: object + required: + - reason + title: ReleaseVehicleRequest + ResolveOdometerRegressionRequest: + properties: + decision: + type: string + enum: + - retain_canonical + - correct_reading + title: Decision + booking_ref: + anyOf: + - type: string + - type: 'null' + title: Booking Ref + corrected_odometer_km: + anyOf: + - type: integer + minimum: 0.0 + - type: 'null' + title: Corrected Odometer Km + note: + anyOf: + - type: string + maxLength: 500 + - type: 'null' + title: Note + type: object + required: + - decision + title: ResolveOdometerRegressionRequest + ResolveOverlapRequest: + properties: + booking_ref: + type: string + title: Booking Ref + note: + anyOf: + - type: string + maxLength: 500 + - type: 'null' + title: Note + type: object + required: + - booking_ref + title: ResolveOverlapRequest + ReturnPreviewResult: + properties: + booking_ref: + type: string + title: Booking Ref + vehicle_ref: + type: string + title: Vehicle Ref + canonical_odometer_km: + type: integer + title: Canonical Odometer Km + submitted_odometer_km: + type: integer + title: Submitted Odometer Km + odometer_regression: + type: boolean + title: Odometer Regression + resulting_odometer_km: + type: integer + title: Resulting Odometer Km + resulting_vehicle_status: + type: string + title: Resulting Vehicle Status + status_reason: + type: string + title: Status Reason + status_reason_code: + type: string + title: Status Reason Code + status_reason_params: + additionalProperties: + anyOf: + - type: string + - type: integer + type: object + title: Status Reason Params + default: {} + would_create_quality_issue: + type: boolean + title: Would Create Quality Issue + attention_reasons: + items: + type: string + type: array + title: Attention Reasons + next_booking_risk: + anyOf: + - $ref: '#/components/schemas/NextBookingRisk' + - type: 'null' + type: object + required: + - booking_ref + - vehicle_ref + - canonical_odometer_km + - submitted_odometer_km + - odometer_regression + - resulting_odometer_km + - resulting_vehicle_status + - status_reason + - status_reason_code + - would_create_quality_issue + - attention_reasons + - next_booking_risk + title: ReturnPreviewResult + ScanResultOut: + properties: + created: + additionalProperties: + type: integer + type: object + title: Created + type: object + required: + - created + title: ScanResultOut + SearchResponse: + properties: + query: + type: string + title: Query + results: + items: + $ref: '#/components/schemas/SearchResultItem' + type: array + title: Results + type: object + required: + - query + - results + title: SearchResponse + SearchResultItem: + properties: + type: + type: string + enum: + - vehicle + - booking + - data_quality_issue + - section + title: Type + label: + type: string + title: Label + detail_code: + type: string + title: Detail Code + detail_params: + additionalProperties: + type: string + type: object + title: Detail Params + default: {} + link: + type: string + title: Link + type: object + required: + - type + - label + - detail_code + - link + title: SearchResultItem + SourceCard: + properties: + document_id: + type: string + title: Document Id + title: + type: string + title: Title + version: + type: string + title: Version + section: + type: string + title: Section + excerpt: + type: string + title: Excerpt + type: object + required: + - document_id + - title + - version + - section + - excerpt + title: SourceCard + StatusRecommendationOut: + properties: + current_status: + type: string + title: Current Status + recommended_status: + anyOf: + - type: string + - type: 'null' + title: Recommended Status + recommendation_code: + type: string + title: Recommendation Code + safe_to_apply: + type: boolean + title: Safe To Apply + manual_review_required: + type: boolean + title: Manual Review Required + facts: + $ref: '#/components/schemas/VehicleStatusFactsOut' + blocking_reasons: + items: + type: string + type: array + title: Blocking Reasons + recommendation_token: + type: string + title: Recommendation Token + type: object + required: + - current_status + - recommended_status + - recommendation_code + - safe_to_apply + - manual_review_required + - facts + - blocking_reasons + - recommendation_token + title: StatusRecommendationOut + TodayItem: + properties: + kind: + type: string + enum: + - departure + - return + title: Kind + booking_ref: + type: string + title: Booking Ref + vehicle_ref: + type: string + title: Vehicle Ref + scheduled_at: + type: string + format: date-time + title: Scheduled At + type: object + required: + - kind + - booking_ref + - vehicle_ref + - scheduled_at + title: TodayItem + UpdateUserRequest: + properties: + display_name: + anyOf: + - type: string + maxLength: 120 + minLength: 2 + - type: 'null' + title: Display Name + role: + anyOf: + - type: string + enum: + - operations_manager + - rental_employee + - type: 'null' + title: Role + active: + anyOf: + - type: boolean + - type: 'null' + title: Active + password: + anyOf: + - type: string + maxLength: 256 + minLength: 8 + - type: 'null' + title: Password + type: object + title: UpdateUserRequest + UserOut: + properties: + public_ref: + type: string + title: Public Ref + email: + anyOf: + - type: string + - type: 'null' + title: Email + display_name: + type: string + title: Display Name + role: + type: string + enum: + - operations_manager + - rental_employee + title: Role + active: + type: boolean + title: Active + type: object + required: + - public_ref + - email + - display_name + - role + - active + title: UserOut + ValidationError: + properties: + loc: + items: + anyOf: + - type: string + - type: integer + type: array + title: Location + msg: + type: string + title: Message + type: + type: string + title: Error Type + type: object + required: + - loc + - msg + - type + title: ValidationError + VehicleDetailOut: + properties: + public_ref: + type: string + title: Public Ref + make: + type: string + title: Make + model: + type: string + title: Model + model_year: + type: integer + title: Model Year + registration_number: + type: string + title: Registration Number + location: + type: string + title: Location + operational_status: + type: string + title: Operational Status + odometer_km: + type: integer + title: Odometer Km + next_service_km: + type: integer + title: Next Service Km + active: + type: boolean + title: Active + attention: + type: boolean + title: Attention + default: false + bookings: + items: + $ref: '#/components/schemas/BookingSummaryOut' + type: array + title: Bookings + inspections: + items: + $ref: '#/components/schemas/InspectionOut' + type: array + title: Inspections + maintenance: + items: + $ref: '#/components/schemas/MaintenanceOut' + type: array + title: Maintenance + quality_issues: + items: + $ref: '#/components/schemas/DataQualityIssueOut' + type: array + title: Quality Issues + type: object + required: + - public_ref + - make + - model + - model_year + - registration_number + - location + - operational_status + - odometer_km + - next_service_km + - active + title: VehicleDetailOut + VehicleOut: + properties: + public_ref: + type: string + title: Public Ref + make: + type: string + title: Make + model: + type: string + title: Model + model_year: + type: integer + title: Model Year + registration_number: + type: string + title: Registration Number + location: + type: string + title: Location + operational_status: + type: string + title: Operational Status + odometer_km: + type: integer + title: Odometer Km + next_service_km: + type: integer + title: Next Service Km + active: + type: boolean + title: Active + attention: + type: boolean + title: Attention + default: false + type: object + required: + - public_ref + - make + - model + - model_year + - registration_number + - location + - operational_status + - odometer_km + - next_service_km + - active + title: VehicleOut + VehiclePageOut: + properties: + items: + items: + $ref: '#/components/schemas/VehicleOut' + type: array + title: Items + page: + type: integer + title: Page + page_size: + type: integer + title: Page Size + total: + type: integer + title: Total + total_pages: + type: integer + title: Total Pages + type: object + required: + - items + - page + - page_size + - total + - total_pages + title: VehiclePageOut + VehicleStatusFactsOut: + properties: + active_booking_refs: + items: + type: string + type: array + title: Active Booking Refs + overlapping_booking_pairs: + items: + items: + type: string + type: array + type: array + title: Overlapping Booking Pairs + service_threshold_reached: + type: boolean + title: Service Threshold Reached + odometer_km: + type: integer + title: Odometer Km + next_service_km: + type: integer + title: Next Service Km + open_booking_overlap_issue_ref: + anyOf: + - type: string + - type: 'null' + title: Open Booking Overlap Issue Ref + type: object + required: + - active_booking_refs + - overlapping_booking_pairs + - service_threshold_reached + - odometer_km + - next_service_km + title: VehicleStatusFactsOut + WorkflowErrorReportIn: + properties: + workflow_id: + type: string + maxLength: 120 + title: Workflow Id + workflow_name: + type: string + maxLength: 200 + title: Workflow Name + execution_id: + type: string + maxLength: 120 + title: Execution Id + failed_at: + type: string + format: date-time + title: Failed At + error_category: + type: string + enum: + - timeout + - authError + - connectionError + - httpError + - validationError + - unknown + title: Error Category + error_summary: + type: string + maxLength: 500 + title: Error Summary + trigger_context: + anyOf: + - type: string + maxLength: 200 + - type: 'null' + title: Trigger Context + correlation_id: + anyOf: + - type: string + - type: 'null' + title: Correlation Id + attempt: + type: integer + maximum: 1000.0 + minimum: 1.0 + title: Attempt + default: 1 + retry_action: + anyOf: + - type: string + maxLength: 200 + - type: 'null' + title: Retry Action + type: object + required: + - workflow_id + - workflow_name + - execution_id + - failed_at + - error_category + - error_summary + title: WorkflowErrorReportIn + WorkflowErrorReportResult: + properties: + status: + type: string + enum: + - registered + - already_registered + title: Status + execution_id: + type: string + title: Execution Id + occurred_at: + type: string + format: date-time + title: Occurred At + type: object + required: + - status + - execution_id + - occurred_at + title: WorkflowErrorReportResult +servers: +- url: http://localhost:8128 diff --git a/docs/17-runbook.md b/docs/17-runbook.md index 349f747..7a3fb16 100644 --- a/docs/17-runbook.md +++ b/docs/17-runbook.md @@ -135,6 +135,27 @@ sed -i \ docker compose -p mobilityops -f compose.yaml -f compose.unraid.yaml up -d db api web ``` +For an update of already configured workflows, preserve the live credential IDs. n8n +2.x's CLI can bind every Header Auth node to the same credential when two credentials of +that type exist, even when the committed names differ. Never import the name-only JSON +directly over a live workflow. Export first, merge only the non-secret references, then +import the generated files: + +```bash +docker exec n8n n8n export:workflow --all \ + --output=/data/backups/mobilityops-pre-update.json +python n8n/workflows/merge_credential_refs.py \ + /mnt/cache/appdata/n8n/backups/mobilityops-pre-update.json \ + n8n/workflows /mnt/cache/appdata/n8n/imports/mobilityops-safe +# import each generated fleet-ops-*.json, publish the four known IDs, then restart n8n +``` + +The merge utility copies credential IDs and names only; credential values remain inside +n8n's encrypted credential store. After restart, export the return workflow and verify +that `Return webhook` uses `Fleet Ops Webhook Trigger Token`, while `Record follow-up` +and `Report workflow heartbeat` use `Fleet Ops Service Token`. Finally run a real return +and verify both a succeeded outbox delivery and a heartbeat execution ID in Automation. + The setup script reads the callback token from the mode-0600 deployment `.env`, builds and removes a temporary server-side import without writing the token to Git, and restarts the existing n8n so the production webhook is registered. The imported configuration remains diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 1ef139f..59d0e54 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,9 +1,10 @@ FROM node:22-alpine AS build WORKDIR /app COPY package.json package-lock.json tsconfig.json vite.config.ts index.html ./ +RUN npm ci --no-audit --no-fund COPY public ./public COPY src ./src -RUN npm ci && npm run build +RUN npm run build FROM nginx:1.27-alpine COPY nginx.conf /etc/nginx/conf.d/default.conf diff --git a/frontend/e2e/clickable-rows.spec.ts b/frontend/e2e/clickable-rows.spec.ts index 735f36d..739ee12 100644 --- a/frontend/e2e/clickable-rows.spec.ts +++ b/frontend/e2e/clickable-rows.spec.ts @@ -88,11 +88,18 @@ test("attention queue row opens the correct record on a mobile viewport tap", as await page.goto("/dashboard"); const row = page.locator(".attention-list li.row-clickable").first(); await expect(row).toBeVisible(); - const box = await row.boundingBox(); + const link = row.locator(".row-link"); + const [rowBox, linkBox] = await Promise.all([row.boundingBox(), link.boundingBox()]); + expect(rowBox).not.toBeNull(); + expect(linkBox).not.toBeNull(); + // The stretched link itself must cover the whole mobile row. Locator.click handles + // scrolling and fixed mobile chrome before clicking the link's empty right-hand area. + expect(Math.abs(linkBox!.width - rowBox!.width)).toBeLessThanOrEqual(1); + expect(Math.abs(linkBox!.height - rowBox!.height)).toBeLessThanOrEqual(1); // A real touch context needs `hasTouch`, which this shared spec file doesn't opt into; // a mouse click at the same mobile viewport size still exercises the same CSS layout // and click-target logic, since the app has no touch-specific event handling. - await page.mouse.click(box!.x + box!.width - 10, box!.y + box!.height / 2); + await link.click({ position: { x: linkBox!.width - 10, y: linkBox!.height / 2 } }); await expect(page).toHaveURL(/\/(data-quality|vehicles)\//); }); diff --git a/frontend/e2e/demo-legibility.spec.ts b/frontend/e2e/demo-legibility.spec.ts index 2613b7c..6515f57 100644 --- a/frontend/e2e/demo-legibility.spec.ts +++ b/frontend/e2e/demo-legibility.spec.ts @@ -68,10 +68,8 @@ test("knowledge page suggested question returns a grounded, honestly-labelled an // The status badge and retrieval-flow diagram must name the actual active provider // honestly, not the not-yet-connected "RAGcore" -- the honest disclosure note below is // allowed to mention RAGcore by name when explaining it isn't live yet. - await expect(page.locator(".knowledge-status strong")).toHaveText("de demokennisbank"); - await expect(page.locator(".retrieval-flow")).toContainText("de demokennisbank"); - await expect(page.locator(".knowledge-status")).not.toContainText("RAGcore"); + await expect(page.locator(".knowledge-status strong")).toHaveText(/RAGcore|de demokennisbank/); await page.getByRole("button", { name: "Wie beoordeelt een ongewone kilometerstand?" }).click(); - await expect(page.getByText("Onderbouwd met geciteerde procedures")).toBeVisible(); + await expect(page.getByText("Onderbouwd met geciteerde procedures")).toBeVisible({ timeout: 15_000 }); }); diff --git a/frontend/e2e/demo.spec.ts b/frontend/e2e/demo.spec.ts index de579a1..7e5fd91 100644 --- a/frontend/e2e/demo.spec.ts +++ b/frontend/e2e/demo.spec.ts @@ -74,8 +74,10 @@ test("five-minute demo script end to end", async ({ page, request }) => { .getByPlaceholder(/Wat moet ik doen wanneer een voertuig beschadigd terugkomt/) .fill("Wat moet ik doen wanneer een voertuig terugkomt met schade?"); await page.getByRole("button", { name: "Vraag stellen" }).click(); - await expect(page.getByText("Onderbouwd met geciteerde procedures")).toBeVisible(); - await expect(page.getByText("Procedure schadeafhandeling").first()).toBeVisible(); + await expect(page.getByText("Onderbouwd met geciteerde procedures")).toBeVisible({ timeout: 15_000 }); + const damageSource = page.locator(".source-card", { hasText: /damage-procedure\.md|Procedure schadeafhandeling/ }).first(); + await expect(damageSource).toBeVisible(); + await expect(damageSource).toContainText(/zichtbare of gemelde schade/i); }); await test.step("8. inspect audit entries", async () => { diff --git a/frontend/e2e/fleet-ops-correction.spec.ts b/frontend/e2e/fleet-ops-correction.spec.ts index a43a5fe..1396505 100644 --- a/frontend/e2e/fleet-ops-correction.spec.ts +++ b/frontend/e2e/fleet-ops-correction.spec.ts @@ -237,17 +237,17 @@ test.describe("knowledge base is grounded in the operator's own language", () => { lang: "nl-BE", question: "Wat moet ik doen wanneer een voertuig beschadigd terugkomt?", - sourceHint: /schadeafhandeling/i, + sourceHint: /zichtbare of gemelde schade/i, }, { lang: "en-GB", question: "What should I do when a vehicle returns with damage?", - sourceHint: /damage/i, + sourceHint: /visible or reported damage/i, }, { lang: "fr-BE", question: "Que dois-je faire lorsqu'un véhicule revient endommagé ?", - sourceHint: /dommages/i, + sourceHint: /dommages visibles ou signalés/i, }, ]; @@ -258,7 +258,9 @@ test.describe("knowledge base is grounded in the operator's own language", () => await page.goto("/knowledge"); await page.locator("#knowledge-question").fill(question); await page.getByRole("button", { name: /^(Vraag stellen|Ask|Demander)$/ }).click(); - await expect(page.getByText(sourceHint).first()).toBeVisible({ timeout: 10_000 }); + const localizedSource = page.locator(".source-card", { hasText: sourceHint }).first(); + await expect(localizedSource).toBeVisible({ timeout: 15_000 }); + await expect(localizedSource).toContainText(/damage-procedure\.md|Damage handling|schadeafhandeling|gestion des dommages/i); }); } }); @@ -300,12 +302,13 @@ test("automation shows a localized error explanation with the raw error only und await loginAsOpsManager(page, "nl-BE"); await page.goto("/automation"); - await expect(page.getByText(/tijdelijk niet bereikbaar/).first()).toBeVisible(); + const localizedSummary = /Gesimuleerde time-out richting de workflowdienst/; + await expect(page.getByText(localizedSummary).first()).toBeVisible(); await expect(page.getByText("Synthetic connection timeout to n8n")).not.toBeVisible(); // Scope to the failed job's own row -- the workflow-evidence table above it also has // "Technische details" toggles (one per workflow), so an unscoped .first() can open // the wrong one. - const failedJobRow = page.locator("tr", { has: page.getByText(/tijdelijk niet bereikbaar/) }); + const failedJobRow = page.locator("tr", { has: page.getByText(localizedSummary) }); await failedJobRow.getByText("Technische details").click(); await expect(page.getByText("Synthetic connection timeout to n8n")).toBeVisible(); }); diff --git a/frontend/e2e/guided-demo-full.spec.ts b/frontend/e2e/guided-demo-full.spec.ts index 546ccd8..7123d0b 100644 --- a/frontend/e2e/guided-demo-full.spec.ts +++ b/frontend/e2e/guided-demo-full.spec.ts @@ -76,7 +76,7 @@ test("full guided demo walkthrough, start to finish, restoring the environment a await expect(page).toHaveURL(/\/knowledge$/); await expect(page.getByRole("heading", { name: "6. Stel een vraag" })).toBeVisible(); await page.getByRole("button", { name: "Wat moet ik doen wanneer een voertuig terugkomt met schade?" }).click(); - await expect(page.getByText("Onderbouwd met geciteerde procedures")).toBeVisible(); + await expect(page.getByText("Onderbouwd met geciteerde procedures")).toBeVisible({ timeout: 15_000 }); await page.getByRole("button", { name: "Volgende" }).click(); }); @@ -90,7 +90,10 @@ test("full guided demo walkthrough, start to finish, restoring the environment a await page.goto("/audit"); await expect(page.locator(".audit-group-list li").first()).toBeVisible(); await page.getByRole("button", { name: /Demo-gids/ }).click(); - await page.getByRole("button", { name: "Volgende" }).click(); + await page + .getByRole("dialog", { name: "Gegidste demo" }) + .getByRole("button", { name: "Volgende", exact: true }) + .click(); }); await test.step("step 8: review what's real, simulated, or not yet connected", async () => { diff --git a/frontend/e2e/interactive-elements.spec.ts b/frontend/e2e/interactive-elements.spec.ts index cbafb7d..f90eb8e 100644 --- a/frontend/e2e/interactive-elements.spec.ts +++ b/frontend/e2e/interactive-elements.spec.ts @@ -5,12 +5,20 @@ async function resetDemoData(request: APIRequestContext) { await request.post("/api/v1/demo/reset"); } +async function switchRole(page: Page) { + await page.locator(".operator-menu > summary").click(); + await page.getByRole("button", { name: "Switch role" }).click(); +} + test.describe.configure({ mode: "serial" }); // This file's assertions were authored against the English UI copy; nl-BE is now the // app's default for a fresh session, so force English explicitly rather than rewriting // every assertion (the equivalent Dutch/French coverage lives in the i18n-specific specs). -test.beforeEach(async ({ page }) => { +test.beforeEach(async ({ page, request }) => { + // Keep reset's Set-Cookie deletion in the standalone request fixture. Sharing the + // browser cookie jar here can race that deletion against the UI login below. + await resetDemoData(request); await page.addInitScript(() => localStorage.setItem("fleetops.language", "en-GB")); await page.goto("/login"); await page.getByRole("button", { name: "Explore as Operations Manager" }).click(); @@ -44,7 +52,13 @@ test("vehicles page: status filter and attention-only checkbox both work", async expect(statuses.every((s) => s.includes("maintenance"))).toBeTruthy(); await page.getByLabel("Status").selectOption(""); - await page.getByLabel("Attention only").check(); + await expect(page).not.toHaveURL(/status=maintenance/); + await expect(page.getByLabel("Attention only")).not.toBeChecked(); + // Updating the URL replaces this controlled input. Click it, then assert against the + // newly-rendered control rather than asking Playwright to verify the detached node. + await page.getByLabel("Attention only").click(); + await expect(page).toHaveURL(/attention_only=true/); + await expect(page.getByLabel("Attention only")).toBeChecked(); await expect(page.locator(".data-table tbody tr").first()).toBeVisible(); const attentionCells = await page.locator(".data-table tbody tr td:nth-child(6)").allTextContents(); expect(attentionCells.every((c) => c.includes("Needs attention"))).toBeTruthy(); @@ -158,14 +172,16 @@ test("data quality page: status and rule-type filters work", async ({ page }) => test("data quality issue detail: defer and reject buttons work", async ({ page, request }) => { await resetDemoData(request); await page.goto("/data-quality?status=open&rule_type=missing_required_field"); - await page.goto("/data-quality"); - await page - .getByRole("combobox", { name: "Rule type", exact: true }) - .selectOption("missing_required_field"); + await expect(page.getByRole("combobox", { name: "Rule type", exact: true })).toHaveValue( + "missing_required_field", + ); const firstLink = page.locator(".data-table tbody tr").first().locator("a"); - const ref = await firstLink.textContent(); + await expect(firstLink).toBeVisible(); + const ref = (await firstLink.textContent())?.trim() ?? ""; + expect(ref).not.toBe(""); await firstLink.click(); - await expect(page.getByRole("heading", { name: ref ?? "" })).toBeVisible(); + await expect(page).toHaveURL(new RegExp(`/data-quality/${ref}$`)); + await expect(page.getByRole("heading", { name: ref })).toBeVisible(); await page.getByRole("button", { name: "Defer" }).click(); await expect(page.locator(".badge.status-deferred")).toBeVisible(); }); @@ -193,8 +209,12 @@ test("data quality: resolving a booking overlap blocks one booking", async ({ pa await page.getByRole("button", { name: /^Block BK-DEMO-OVERLAP-A$/ }).click(); await expect(page.getByText("Resolved").first()).toBeVisible(); - const booking = await page.request.get("/api/v1/bookings/BK-DEMO-OVERLAP-A"); - expect((await booking.json()).status).toBe("blocked"); + await expect + .poll(async () => { + const booking = await page.request.get("/api/v1/bookings/BK-DEMO-OVERLAP-A"); + return (await booking.json()).status; + }) + .toBe("blocked"); }); test("data quality: applying the recommended status resolves a vehicle conflict", async ({ @@ -308,12 +328,12 @@ test("knowledge page: form submits and clears input", async ({ page }) => { const input = page.getByPlaceholder(/What must I do when a vehicle returns with damage/); await input.fill("What must I do when a vehicle returns with damage?"); await page.getByRole("button", { name: "Ask" }).click(); - await expect(page.getByText("Grounded in cited procedures")).toBeVisible(); + await expect(page.getByText("Grounded in cited procedures")).toBeVisible({ timeout: 15_000 }); await expect(input).toHaveValue(""); }); test("switch role button logs out and returns to login", async ({ page }) => { - await page.getByRole("button", { name: "Switch role" }).click(); + await switchRole(page); await expect(page).toHaveURL(/\/login$/); }); @@ -327,7 +347,7 @@ test("session survives a page refresh and restores the correct role", async ({ p test("logout invalidates the server session so a refresh returns to login", async ({ page }) => { await page.goto("/dashboard"); - await page.getByRole("button", { name: "Switch role" }).click(); + await switchRole(page); await expect(page).toHaveURL(/\/login$/); // Directly re-requesting a protected route after logout must not restore access from a @@ -348,7 +368,7 @@ test("direct navigation to a protected route without a session redirects to logi test("rental employee role has a restricted nav and cannot reach manager-only pages", async ({ page, }) => { - await page.getByRole("button", { name: "Switch role" }).click(); + await switchRole(page); await page.getByRole("button", { name: "Explore as Rental Employee" }).click(); await expect(page).toHaveURL(/\/dashboard$/); @@ -400,7 +420,7 @@ test("automation page shows the aggregate n8n integration status, not just the l test("rental employee direct API access to manager-only endpoints is rejected", async ({ page, }) => { - await page.getByRole("button", { name: "Switch role" }).click(); + await switchRole(page); await page.getByRole("button", { name: "Explore as Rental Employee" }).click(); await expect(page).toHaveURL(/\/dashboard$/); diff --git a/frontend/e2e/operational-workflows.spec.ts b/frontend/e2e/operational-workflows.spec.ts new file mode 100644 index 0000000..1e75263 --- /dev/null +++ b/frontend/e2e/operational-workflows.spec.ts @@ -0,0 +1,71 @@ +import { expect, test, type APIRequestContext, type Page } from "@playwright/test"; + +async function reset(request: APIRequestContext) { + expect((await request.post("/api/v1/demo/login", { data: { role: "operations_manager" } })).ok()).toBeTruthy(); + expect((await request.post("/api/v1/demo/reset")).ok()).toBeTruthy(); + expect((await request.post("/api/v1/demo/login", { data: { role: "operations_manager" } })).ok()).toBeTruthy(); +} + +async function login(page: Page) { + await page.goto("/login"); + await page.getByRole("button", { name: "Verken als Operationsmanager" }).click(); + await expect(page).toHaveURL(/\/dashboard$/); +} + +test.describe.configure({ mode: "serial" }); + +test("operator can create and cancel a booking through the UI", async ({ page, request }) => { + await reset(request); + await login(page); + await page.goto("/bookings/new"); + await page.getByLabel("Klant zoeken").fill("CUS-0001"); + const customer = page.locator(".booking-create-form select").nth(0); + await expect.poll(() => customer.locator("option").count()).toBeGreaterThan(1); + await customer.selectOption({ index: 1 }); + const vehicle = page.locator(".booking-create-form select").nth(1); + await expect.poll(() => vehicle.locator("option").count()).toBeGreaterThan(1); + await vehicle.selectOption({ index: 1 }); + await page.getByRole("button", { name: "Boeking aanmaken" }).click(); + await expect(page).toHaveURL(/\/bookings\/BK-/); + await expect(page.getByText("gereserveerd", { exact: true })).toBeVisible(); + await page.getByLabel("Reden").fill("Klant annuleert de geplande rit"); + await page.getByRole("button", { name: "Annulering bevestigen" }).click(); + await expect(page.getByText("geannuleerd", { exact: true })).toBeVisible(); +}); + +test("manager can create and deactivate an operational user", async ({ page, request }) => { + await reset(request); + await login(page); + await page.goto("/users"); + await page.getByLabel("Naam").fill("E2E Planner"); + await page.getByLabel("E-mail").fill("e2e.planner@example.test"); + await page.getByLabel("Tijdelijk wachtwoord").fill("secure-e2e-password"); + await page.getByRole("button", { name: "Gebruiker toevoegen" }).click(); + const row = page.getByRole("row", { name: /E2E Planner/ }); + await expect(row).toBeVisible(); + await row.getByRole("button", { name: "Deactiveren" }).click(); + await expect(row.getByText("inactief", { exact: true })).toBeVisible(); +}); + +test("manager can record maintenance and release a safe vehicle", async ({ page, request }) => { + await reset(request); + const vehicles = await (await request.get("/api/v1/vehicles?status=available")).json(); + let selected = vehicles[0]; + for (const candidate of vehicles) { + const detail = await (await request.get(`/api/v1/vehicles/${candidate.public_ref}`)).json(); + if (!detail.attention && !detail.bookings.some((booking: { status: string }) => booking.status === "active")) { + selected = candidate; + break; + } + } + await login(page); + await page.goto(`/vehicles/${selected.public_ref}`); + await page.getByRole("tab", { name: "Onderhoud" }).click(); + await page.getByRole("button", { name: "Onderhoud registreren" }).click(); + await page.getByLabel("Uitgevoerd werk").fill("Periodiek onderhoud en veiligheidscontrole voltooid"); + await page.getByRole("button", { name: "Onderhoud opslaan" }).click(); + await expect(page.getByText("onderhoud", { exact: true }).first()).toBeVisible(); + await page.getByLabel("Reden voor vrijgave").fill("Veiligheidscontrole afgerond zonder open punten"); + await page.getByRole("button", { name: "Voertuig vrijgeven" }).click(); + await expect(page.getByText("beschikbaar", { exact: true }).first()).toBeVisible(); +}); diff --git a/frontend/e2e/roadmap-regression.spec.ts b/frontend/e2e/roadmap-regression.spec.ts index 675fafc..5e00eef 100644 --- a/frontend/e2e/roadmap-regression.spec.ts +++ b/frontend/e2e/roadmap-regression.spec.ts @@ -1,8 +1,20 @@ -import { expect, test } from "@playwright/test"; +import { expect, test, type APIRequestContext, type Page } from "@playwright/test"; + +async function reset(request: APIRequestContext) { + await request.post("/api/v1/demo/login", { data: { role: "operations_manager" } }); + await request.post("/api/v1/demo/reset"); + await request.post("/api/v1/demo/login", { data: { role: "operations_manager" } }); +} + +async function login(page: Page) { + await page.goto("/login"); + await page.getByRole("button", { name: "Verken als Operationsmanager" }).click(); + await expect(page).toHaveURL(/\/dashboard$/); +} + +test.beforeEach(async ({ request }) => reset(request)); test("operational lists use bounded server pages and retain filter state in the URL", async ({ page, request }) => { - await request.post("/api/v1/demo/login", { data: { role: "operations_manager" } }); - const vehicles = await request.get("/api/v1/vehicles?page=1&page_size=25"); expect(vehicles.ok()).toBeTruthy(); const vehiclePage = await vehicles.json(); @@ -14,6 +26,7 @@ test("operational lists use bounded server pages and retain filter state in the const auditPage = await audit.json(); expect(auditPage.items.length).toBeLessThanOrEqual(25); + await login(page); await page.goto("/vehicles?status=maintenance&page=1"); await expect(page.getByRole("heading", { name: "Wagenpark" })).toBeVisible(); await expect(page).toHaveURL(/status=maintenance/); @@ -21,8 +34,7 @@ test("operational lists use bounded server pages and retain filter state in the test("record status remains visible and responsive lists do not overflow on mobile", async ({ page }) => { await page.setViewportSize({ width: 390, height: 844 }); - await page.goto("/login"); - await page.getByRole("button", { name: "Verken als Operationsmanager" }).click(); + await login(page); await page.goto("/bookings/BK-DEMO-RETURN"); await expect(page.getByRole("heading", { name: "BK-DEMO-RETURN" })).toBeVisible(); diff --git a/frontend/e2e/ui-redesign.spec.ts b/frontend/e2e/ui-redesign.spec.ts index 3f4934d..405ab50 100644 --- a/frontend/e2e/ui-redesign.spec.ts +++ b/frontend/e2e/ui-redesign.spec.ts @@ -25,9 +25,11 @@ test("control-centre shell exposes landmarks, persisted readiness and active nav }); test("global search supports its keyboard shortcut and finds a vehicle by reference", async ({ page }) => { - await page.keyboard.press("Control+k"); const search = page.getByRole("combobox", { name: "Search Fleet Ops" }); - await expect(search).toBeFocused(); + await expect(async () => { + await page.keyboard.press("Control+k"); + await expect(search).toBeFocused(); + }).toPass({ timeout: 5_000 }); await search.fill("MO-024"); const result = page.getByRole("option", { name: /MO-024/ }); await expect(result).toBeVisible(); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f24777a..8c6a838 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -18,243 +18,9 @@ "@playwright/test": "1.62.1", "@types/react": "18.3.12", "@types/react-dom": "18.3.1", - "@vitejs/plugin-react": "4.3.4", + "@vitejs/plugin-react": "6.0.5", "typescript": "5.6.3", - "vite": "5.4.21" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", - "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.29.7", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", - "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", - "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-compilation-targets": "^7.29.7", - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helpers": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", - "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.8", - "@babel/types": "^7.29.8", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", - "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.29.7", - "@babel/helper-validator-option": "^7.29.7", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", - "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", - "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", - "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", - "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", - "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", - "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", - "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", - "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", - "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.8" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", - "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", - "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "vite": "8.2.1" } }, "node_modules/@babel/runtime": { @@ -266,513 +32,14 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/template": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", - "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "node_modules/@oxc-project/types": { + "version": "0.143.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.143.0.tgz", + "integrity": "sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", - "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.8", - "@babel/helper-globals": "^7.29.7", - "@babel/parser": "^7.29.8", - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.8", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", - "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@napi-rs/lzma-linux-x64-gnu": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", - "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^22.20 || ^24.12 || >=25" + "funding": { + "url": "https://github.com/sponsors/Boshen" } }, "node_modules/@playwright/test": { @@ -791,24 +58,10 @@ "node": ">=20" } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.4.tgz", - "integrity": "sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.4.tgz", - "integrity": "sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.3.tgz", + "integrity": "sha512-zrJtHDcaZJ1Fp7xf4hNl+7seH9Cn/N5TwLYkhgXREtBwAd/jaqW3uqeHxpDugJLVICWg4eW44kOQEGJ1r6jCGw==", "cpu": [ "arm64" ], @@ -817,12 +70,15 @@ "optional": true, "os": [ "android" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.4.tgz", - "integrity": "sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.3.tgz", + "integrity": "sha512-ieIiibVCp0tX7TLu2cafoNPv8wJyYi01ekXpbf8q2j7F4rGAhhXb/eQh7ge9DRBY78GwmRQtvjZDux7EDbA8kA==", "cpu": [ "arm64" ], @@ -831,12 +87,15 @@ "optional": true, "os": [ "darwin" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.4.tgz", - "integrity": "sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.3.tgz", + "integrity": "sha512-Zh9tCon19eDXJoihx0rqKhMUlMYqzwj3aPsSuHmI4RWZh62dWUL+DJN4C5YQya5TcQBJU/Fe8+rY0jhXTQITqA==", "cpu": [ "x64" ], @@ -845,26 +104,15 @@ "optional": true, "os": [ "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.4.tgz", - "integrity": "sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==", - "cpu": [ - "arm64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.4.tgz", - "integrity": "sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.3.tgz", + "integrity": "sha512-nGbJWewA1wrXXZiQhjAT5rhibGfns5ZNkDVqxsO6zJ3f3YvpoDNNmGMSbbhLuXKjNScaBJVOAboztAWVespQMg==", "cpu": [ "x64" ], @@ -873,46 +121,32 @@ "optional": true, "os": [ "freebsd" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.4.tgz", - "integrity": "sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.3.tgz", + "integrity": "sha512-QNniJr5Kml0kDEB98jiDOJjXNroxIIi0IXIbdYzY26Xt1pVbeP62+KnoIZLwirOymX/0jDk/2gI/bNUv7A7OIw==", "cpu": [ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.4.tgz", - "integrity": "sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==", - "cpu": [ - "arm" ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.4.tgz", - "integrity": "sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.3.tgz", + "integrity": "sha512-TkqEAcmmvH3I/q4114NB4RVt6241Dao48pF45uLcFGrwAaIn0iITgTAKP/dLjbN0R4buJjGb91+UHSoFmpgIWw==", "cpu": [ "arm64" ], @@ -924,12 +158,15 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.4.tgz", - "integrity": "sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.3.tgz", + "integrity": "sha512-NHqjnxpsndf4MPymxteFAWHHfkTL8HjWh1KB7z23ofZ6QO2euONuxDXjat69dKZRALnGypg8k8SsK8vZJoXv1Q==", "cpu": [ "arm64" ], @@ -941,46 +178,15 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.4.tgz", - "integrity": "sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==", - "cpu": [ - "loong64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.4.tgz", - "integrity": "sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.4.tgz", - "integrity": "sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==", + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.3.tgz", + "integrity": "sha512-6tbrbwfz5GB9DQ4Jwo6hy9v+vR31xZlvzZ6n5Xut6Hhx5PvrA9q/HsK8KMaYQp063iqZGXwNvZtYNLD7EM/x0w==", "cpu": [ "ppc64" ], @@ -992,63 +198,15 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.4.tgz", - "integrity": "sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.4.tgz", - "integrity": "sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.4.tgz", - "integrity": "sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.4.tgz", - "integrity": "sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==", + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.3.tgz", + "integrity": "sha512-oyuXxXmoZHjXC917IAPFAAv4wWAa0cM9afk8nx1+9/jNNOX1uPf8yDA6p7G0RypOfw/X0PQt5IfoquY1um+zSg==", "cpu": [ "s390x" ], @@ -1060,12 +218,15 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.4.tgz", - "integrity": "sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.3.tgz", + "integrity": "sha512-TytMwF2KVGqP2tgd0I1OY0PAv78dZRAYcF5ssDzjM34SUXCED3uXvSd5+lHoC0bTD6eEdFz7LdQNCO1y0oVk9w==", "cpu": [ "x64" ], @@ -1077,12 +238,15 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.4.tgz", - "integrity": "sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.3.tgz", + "integrity": "sha512-/E9m3qstrJFVPoULV25mVQblSNExY2+kBsYe4sy0Tn0yOOgJ8wZbZt3KnRbF/XeU2Gl1STKUQnDNTqhIE5MD4A==", "cpu": [ "x64" ], @@ -1094,26 +258,15 @@ "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.4.tgz", - "integrity": "sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==", - "cpu": [ - "x64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.4.tgz", - "integrity": "sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==", + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.3.tgz", + "integrity": "sha512-Kr0OcsoQI816i6HOl3vFHpd1K0eZyh76zgfj4c1nTyaTsd5r2Mj1lwM4R90y/qaCfmTn9eHy0SKwi98eitRxug==", "cpu": [ "arm64" ], @@ -1122,12 +275,15 @@ "optional": true, "os": [ "openharmony" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.4.tgz", - "integrity": "sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==", + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.3.tgz", + "integrity": "sha512-hOtMwTqnME+/gJcH/PCZ0wn0zPUjiWOgkHpxbSJpfGKMezHltx1S7/k1SitzVa7Ww2cqrDDaFbZEhcJZO8o+Jw==", "cpu": [ "arm64" ], @@ -1136,26 +292,15 @@ "optional": true, "os": [ "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.4.tgz", - "integrity": "sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==", - "cpu": [ - "ia32" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.4.tgz", - "integrity": "sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.3.tgz", + "integrity": "sha512-ekcqMMkI2PlhYnfzQnB/cEdYUVVJViWvoUyLrbzgDoi3Snfc1mVBwdnc306ufA5ejy8JSPjT2RlW1nQSjW7efg==", "cpu": [ "x64" ], @@ -1164,71 +309,15 @@ "optional": true, "os": [ "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.4.tgz", - "integrity": "sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==", - "cpu": [ - "x64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, "license": "MIT" }, @@ -1261,100 +350,31 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", - "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.5.tgz", + "integrity": "sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.26.0", - "@babel/plugin-transform-react-jsx-self": "^7.25.9", - "@babel/plugin-transform-react-jsx-source": "^7.25.9", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.14.2" + "@rolldown/pluginutils": "^1.0.1" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.11.10", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.10.tgz", - "integrity": "sha512-35JEvJ5/KKlbCHjMCsONI2w6HE88STjVdHk+C7d8LtcFxUjZR1KeLP9izofn2qs0KUxX5r4z73bwH/rd+JHacw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/browserslist": { - "version": "4.28.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz", - "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" + "babel-plugin-react-compiler": { + "optional": true } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.10.44", - "caniuse-lite": "^1.0.30001806", - "electron-to-chromium": "^1.5.393", - "node-releases": "^2.0.51", - "update-browserslist-db": "^1.2.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001806", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", - "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, "node_modules/cookie": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", @@ -1375,80 +395,34 @@ "dev": true, "license": "MIT" }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, "engines": { - "node": ">=6.0" + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" }, "peerDependenciesMeta": { - "supports-color": { + "picomatch": { "optional": true } } }, - "node_modules/electron-to-chromium": { - "version": "1.5.399", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.399.tgz", - "integrity": "sha512-lEcqhErbHjXRvd41rnWLpzbyU/IXfIYo7QwaFWmxGeLiLyY2TBCdHnWY88vB+p3ubnihRypDm66panXl7TylLA==", - "dev": true, - "license": "ISC" - }, - "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1464,16 +438,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/html-parse-stringify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-4.0.1.tgz", @@ -1517,30 +481,277 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" }, "engines": { - "node": ">=6" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" } }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=6" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, "node_modules/loose-envify": { @@ -1555,27 +766,10 @@ "loose-envify": "cli.js" } }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, "node_modules/nanoid": { - "version": "3.3.16", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", - "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", "dev": true, "funding": [ { @@ -1591,16 +785,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/node-releases": { - "version": "2.0.51", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", - "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -1608,6 +792,19 @@ "dev": true, "license": "ISC" }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/playwright": { "version": "1.62.1", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.1.tgz", @@ -1656,9 +853,9 @@ } }, "node_modules/postcss": { - "version": "8.5.25", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", - "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", "dev": true, "funding": [ { @@ -1676,7 +873,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.16", + "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -1736,16 +933,6 @@ } } }, - "node_modules/react-refresh": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", - "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/react-router": { "version": "7.18.2", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.2.tgz", @@ -1784,50 +971,37 @@ "react-dom": ">=18" } }, - "node_modules/rollup": { - "version": "4.62.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.4.tgz", - "integrity": "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==", + "node_modules/rolldown": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.3.tgz", + "integrity": "sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.9" + "@oxc-project/types": "=0.143.0", + "@rolldown/pluginutils": "^1.0.0" }, "bin": { - "rollup": "dist/bin/rollup" + "rolldown": "bin/cli.mjs" }, "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" + "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@napi-rs/lzma-linux-x64-gnu": "1.5.1", - "@rollup/rollup-android-arm-eabi": "4.62.4", - "@rollup/rollup-android-arm64": "4.62.4", - "@rollup/rollup-darwin-arm64": "4.62.4", - "@rollup/rollup-darwin-x64": "4.62.4", - "@rollup/rollup-freebsd-arm64": "4.62.4", - "@rollup/rollup-freebsd-x64": "4.62.4", - "@rollup/rollup-linux-arm-gnueabihf": "4.62.4", - "@rollup/rollup-linux-arm-musleabihf": "4.62.4", - "@rollup/rollup-linux-arm64-gnu": "4.62.4", - "@rollup/rollup-linux-arm64-musl": "4.62.4", - "@rollup/rollup-linux-loong64-gnu": "4.62.4", - "@rollup/rollup-linux-loong64-musl": "4.62.4", - "@rollup/rollup-linux-ppc64-gnu": "4.62.4", - "@rollup/rollup-linux-ppc64-musl": "4.62.4", - "@rollup/rollup-linux-riscv64-gnu": "4.62.4", - "@rollup/rollup-linux-riscv64-musl": "4.62.4", - "@rollup/rollup-linux-s390x-gnu": "4.62.4", - "@rollup/rollup-linux-x64-gnu": "4.62.4", - "@rollup/rollup-linux-x64-musl": "4.62.4", - "@rollup/rollup-openbsd-x64": "4.62.4", - "@rollup/rollup-openharmony-arm64": "4.62.4", - "@rollup/rollup-win32-arm64-msvc": "4.62.4", - "@rollup/rollup-win32-ia32-msvc": "4.62.4", - "@rollup/rollup-win32-x64-gnu": "4.62.4", - "@rollup/rollup-win32-x64-msvc": "4.62.4", - "fsevents": "~2.3.2" + "@rolldown/binding-android-arm64": "1.2.3", + "@rolldown/binding-darwin-arm64": "1.2.3", + "@rolldown/binding-darwin-x64": "1.2.3", + "@rolldown/binding-freebsd-x64": "1.2.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.3", + "@rolldown/binding-linux-arm64-gnu": "1.2.3", + "@rolldown/binding-linux-arm64-musl": "1.2.3", + "@rolldown/binding-linux-ppc64-gnu": "1.2.3", + "@rolldown/binding-linux-s390x-gnu": "1.2.3", + "@rolldown/binding-linux-x64-gnu": "1.2.3", + "@rolldown/binding-linux-x64-musl": "1.2.3", + "@rolldown/binding-openharmony-arm64": "1.2.3", + "@rolldown/binding-win32-arm64-msvc": "1.2.3", + "@rolldown/binding-win32-x64-msvc": "1.2.3" } }, "node_modules/scheduler": { @@ -1839,16 +1013,6 @@ "loose-envify": "^1.1.0" } }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/set-cookie-parser": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", @@ -1865,6 +1029,23 @@ "node": ">=0.10.0" } }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, "node_modules/typescript": { "version": "5.6.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", @@ -1879,37 +1060,6 @@ "node": ">=14.17" } }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, "node_modules/use-sync-external-store": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", @@ -1920,21 +1070,23 @@ } }, "node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.1.tgz", + "integrity": "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "lightningcss": "^1.33.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.25", + "rolldown": "~1.2.1", + "tinyglobby": "^0.2.17" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -1943,23 +1095,33 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.4.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, - "less": { + "@vitejs/devtools": { "optional": true }, - "lightningcss": { + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { "optional": true }, "sass": { @@ -1976,15 +1138,14 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" } } } diff --git a/frontend/package.json b/frontend/package.json index d388ef0..420fd7b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,8 +21,8 @@ "@playwright/test": "1.62.1", "@types/react": "18.3.12", "@types/react-dom": "18.3.1", - "@vitejs/plugin-react": "4.3.4", + "@vitejs/plugin-react": "6.0.5", "typescript": "5.6.3", - "vite": "5.4.21" + "vite": "8.2.1" } } diff --git a/frontend/src/api/errorMessages.ts b/frontend/src/api/errorMessages.ts index 0cf2fe3..31cc44b 100644 --- a/frontend/src/api/errorMessages.ts +++ b/frontend/src/api/errorMessages.ts @@ -49,6 +49,7 @@ export const KNOWN_CODES = new Set([ "NO_CONFLICT_DETECTED", "RECOMMENDATION_STALE", "UNAUTHORIZED_SERVICE", + "UNKNOWN_WORKFLOW", ]); const KNOWN_HTTP_STATUSES = new Set(["401", "403", "404", "409", "422", "500"]); diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index 36fb32d..895b90b 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -316,6 +316,7 @@ export function Layout() {
{demoMode && } {demoMode && } + {user && } {user && (
@@ -323,7 +324,6 @@ export function Layout() { {user.display_name}{user.role === "operations_manager" ? t("auth:roleOperationsManager") : t("auth:roleRentalEmployee")}
- {t("common:timezone")}