Skip to content

Choose the admin stack #1

Closed
Closed

Description

@ABilenduke

Choose the admin stack: language, runtime, framework, linters, code quality and security checks, and its Docker container. Part of the tech stack epic in commerce.

Status

Stage Track Next action Branch PR
done full Write the stack ADR through a PR — —

Appetite: 2h active (Default) · Estimate: — · Active so far: see the time comment

Brief

Idea: "Determine the best tech stack for admin. I prefer TypeScript and Vue in some form. Also linters, code quality and security checks. Each service will be in a Docker container."

Problem and audience: Admin is the restricted application staff use to run the store. It has:

  • data-heavy screens: catalog, orders, refunds, subscriptions, roles and audit;
  • live alerts;
  • a UI that mirrors backend permissions without enforcing them;
  • no need for SEO.

Outcome: A recorded choice of framework, data, form and table libraries, the session and permission approach, test tools and the serving container. Every requirement below is met, or its gap is named.

Scope.

  • In: a Vue + Vite SPA, or Nuxt with ssr: false.
  • Out: Ionic, Quasar or Vuetify UI kits, because the design system supplies components. A native app (web only, Default).
  • Deferred: an ADR. The shared toolchain and CI security checks come from the epic, commerce#3.

Constraints:

  • Hiding a button is not authorization; the backend enforces every permission (AGENTS.md:8, README.md:20).
  • Only the API through the generated SDK, plus real-time events (AGENTS.md:10).
  • Sensitive actions go through audited backend operations (AGENTS.md:15).
  • Each role sees only what it needs (AGENTS.md:17).
  • Staff roles (README.md:12).
  • Live alerts (README.md:15).
  • Components come from the design system (README.md:21).

Known in code: None. The repo holds docs only.

Track: full — the libraries need comparing against outside sources · Appetite: 2h active (Default)

ID Research question Track Why it matters to the plan
RQ-1 Vue + Vite SPA or Nuxt with ssr: false, given that the design system supplies components? technical The framework
RQ-2 Which libraries for the data-heavy UI: tables (TanStack Table), forms and validation (VeeValidate or FormKit with Zod or Valibot), charts, typed routing, and Pinia with a query layer? technical The core libraries
RQ-3 How to handle the session and permissions: a cookie session with CSRF protection, UI that mirrors backend RBAC, and no tokens in localStorage? technical The security model in the browser
RQ-4 How to handle real-time alerts and the audit views, and which test tools (Vitest browser mode or Playwright component tests)? technical Live alerts and the test pyramid
RQ-5 How to serve it as a static container, with security headers and a CSP? technical The container

Assumptions: Web only (Default).

Spec

Not written yet.

Plan

Not written yet.

Decisions

ID Decision Kind Source
D1 A Vue 3.5 + Vite 8 SPA with Vue Router 5 typed file-based routing. No Nuxt. Decided @ABilenduke, 2026-10-07; research RQ-1
D2 Libraries: TanStack Table 9 + Virtual behind an admin-owned DataTable; TanStack Form with Zod 4; ECharts 6 via vue-echarts, tree-shaken, with aria on; Pinia 4 + Pinia Colada, holding no persisted server data. Zod schemas are generated by Orval in zod-only mode from the backend's spec (commerce#3 DE-8, decided 2026-10-08). Decided @ABilenduke, 2026-10-07; research RQ-2
D3 Session and permissions: a cookie-only session, with nothing in Web Storage. A flat permission list from /me, route guards and v-if; no CASL. Server-enforced timeouts and step-up re-auth. The staff session uses its own __Host- cookie with SameSite=Strict, behind an admin-only CORS allowlist and Origin checks (commerce#3 DE-9, decided 2026-10-08). Decided @ABilenduke, 2026-10-07; research RQ-3
D4 Real-time and tests: VueUse useWebSocket with backoff, jitter, a heartbeat and a refetch on reconnect. Vitest 5 (Node + Browser Mode), MSW 3 and a thin Playwright E2E suite. No Playwright component testing. Decided @ABilenduke, 2026-10-07; research RQ-4
D5 Container: nginx-unprivileged alpine-slim, read-only, with a header CSP and no unsafe-inline, immutable caching for /assets/ and no-cache for index.html. Decided @ABilenduke, 2026-10-07; research RQ-5; commerce#3 DE-5

ADR: to be written through a PR in this repo.

Links

Activity

  1. added
    type:spikeResearch that answers a question and ends there
    track:fullNeeds evidence: brief, research, plan, execute, review
    on Oct 7, 2026
  2. ABilenduke commented on Oct 7, 2026

    @ABilenduke
    ContributorAuthor

    Time: issue-1

    Generated by worklog render from time.jsonl. Do not edit by hand.

    Step Estimate Started Finished Wall Active Sessions
    brief 15m 2026-10-07 00:04 2026-10-07 00:04 0m — 3da8d26d (claude-code)
    research 45m 2026-10-07 00:37 2026-10-07 00:39 2m 2m 3da8d26d (claude-code)
    Total 1h 3m 2m

    Active time counts agent turns plus up to 5 minutes of each wait for a person, and no single gap
    counts more than 30 minutes. — means nothing was recorded.

    workflow · claude-code · 2026-10-07 00:55

  3. ABilenduke commented on Oct 7, 2026

    @ABilenduke
    ContributorAuthor

    Research: Choose the admin stack

    Status: Answered (the session cookie, CSRF and WebSocket details depend on the backend spike) · Evidence base: a89f48b (clean), plus d0573a0 for the parent docs · 2026-10-07

    Summary

    Build the admin as a plain Vue 3.5 + Vite 8 single-page app with Vue Router 5. Vue Router 5 now includes typed file-based routing.

    Nuxt with ssr: false adds 66 direct dependencies, telemetry, auto-imports and inline bootstrap scripts that complicate a strict CSP. It buys nothing for an app with no SEO or server rendering.

    Use headless libraries, so the design system's components render everything:

    • TanStack Table and Virtual for the data grids;
    • TanStack Form with Zod 4 for forms;
    • ECharts for accessible charts;
    • Pinia with Pinia Colada for server state.

    Mirror backend permissions with a flat permission list from /me, route guards and v-if; CASL isn't needed. The session lives only in an HttpOnly cookie. Live alerts use VueUse useWebSocket and refetch after every reconnect.

    Test with Vitest 5, in Node for logic and in Browser Mode for components, plus MSW and a thin Playwright suite. Serve the app from nginx-unprivileged with a strict header CSP.

    Confidence is high for the framework, server and session principles, and medium for the library picks: several are brand-new majors.

    Answers

    RQ Answer Confidence
    RQ-1 A Vue 3.5 + Vite 8 SPA with Vue Router 5. Vue Router 5 merged unplugin-vue-router, so it has typed file-based routing built in. Nuxt ssr: false adds 66 direct dependencies (Nitro, devtools, telemetry), auto-imports and inline window.__NUXT__ scripts, for no gain without SSR or SEO. High
    RQ-2 Tables: TanStack Table 9 + TanStack Virtual, both headless.
    Forms: TanStack Form with Standard Schema and Zod 4, which the SDK codegen can emit. VeeValidate's Zod adapter is still on Zod 3, and its v5 beta is stale.
    Charts: ECharts 6 via vue-echarts, tree-shaken, with aria on.
    State: Pinia 4 + Pinia Colada; TanStack Query is a close second.
    Medium
    RQ-3 The session is a __Host- cookie (HttpOnly, Secure, SameSite=Strict), with nothing in Web Storage. Every *.rsl-commerce.test host is same-site, so SameSite doesn't stop CSRF here. The backend needs an Origin/CORS allowlist plus a custom header or token. A flat permission set from /me, with route guards and v-if, is enough; CASL is not needed. The server enforces timeouts and step-up re-auth. High on principles, medium on values
    RQ-4 Alerts: VueUse useWebSocket with backoff, jitter and a heartbeat, refetching on reconnect.
    Audit lists: manual pagination (pageCount: -1), an infinite query and virtualization.
    Tests: Vitest 5 Browser Mode (Playwright provider) for components, Node Vitest for logic, MSW 3 and a thin Playwright E2E suite. Playwright component testing for Vue is discontinued.
    Medium-high
    RQ-5 nginx-unprivileged alpine-slim: non-root, port 8080, about 8 MB compressed.
    • SPA fallback with try_files $uri /index.html.
    • A header CSP with script-src 'self'; style-src 'self'; frame-ancestors 'none'.
    • /assets/* cached immutable; index.html sent with no-cache.
    Caddy runs as root by default and needs its admin API and auto-HTTPS turned off.
    High

    Recommendation

    Framework: Vue 3.5 + Vite 8 + Vue Router 5 + TypeScript (F4-F10).

    • Typed file routing is first-party (F5).
    • The build is static with no inline script, so a strict CSP is just a header (F9, F48).
    • The trade-off is that the ADR must define the folder layout (src/pages, src/features/<domain>, src/api) and require explicit imports.
    • Option B, Nuxt 4.6 with ssr: false: stronger conventions, but it brings 66 direct dependencies, telemetry and auto-imports (F6, F7), plus inline scripts that need a new CSP hash on every build (F8). That conflicts with AGENTS.md:29.

    Libraries

    Need Pick Why, and the runner-up
    Tables TanStack Table 9 + Virtual 3, behind an admin-owned DataTable Headless, so it renders design-system components. The design system holds no app-specific components (F2, F11, F12).
    Forms TanStack Form with Standard Schema (F14) VeeValidate's Zod adapter peers zod ^3.24, and Standard Schema is only in a stale v5 beta (F13). FormKit's own DOM and themes overlap with the design system (F15).
    Validation Zod 4 Both Hey API and orval generate it. Valibot is smaller, but only Hey API generates it (F16-F18). Generated schemas check shape only; the backend still validates.
    Charts ECharts 6 + vue-echarts 8, from echarts/core, with aria and decals on and charts lazy-loaded by route It has built-in accessibility, and its styles inject through CSSStyleSheet(), which works under a strict CSP (F19). Chart.js is about 5× smaller, but has no built-in accessibility and no release in 12 months (F20).
    State Pinia 4 for client state, Pinia Colada 1.x for server cache, held in memory only and cleared on logout Matches the storefront's choice (commerce-storefront#1). TanStack Query Vue 5 is equally viable (F21).

    Session and permissions (F24-F33)

    • Cookie: the backend sets __Host-…; Secure; HttpOnly; SameSite=Strict; Path=/ with no Domain. The SDK uses credentials: 'include'. Nothing goes in Web Storage or IndexedDB (F28, F29).

    • CSRF, enforced by the backend:

      • an exact-origin CORS allowlist with credentials;
      • an Origin check on unsafe methods;
      • a custom header or a signed double-submit token.

      Sec-Fetch-Site: same-site is untrusted, because the storefront, checkout and docs hosts are sibling subdomains (F26, F27).

    • Permissions:

      • /me returns flat strings such as refund:create;
      • a Pinia can() feeds one beforeEach that reads a typed meta.permission, plus v-if in templates;
      • any 403 refreshes /me.

      CASL only pays off if the backend emits CASL rules with conditions (F30-F33).

    • Timeouts: the server enforces them, and the UI warns using useIdle and the expiry from /me.

      • Proposed values: 15 min idle and 8 h absolute.
      • Step-up re-auth within about 5 min for refunds, role changes, cancellations and inventory edits.
      • On reauth_required, the UI prompts the user, then retries with the same Idempotency-Key.

    Real-time. An alerts store wraps useWebSocket, with autoReconnect.delay as capped exponential backoff plus jitter, and heartbeat on. It invalidates queries on reconnect, because nothing replays missed events (F35, F40).

    Tests. Vitest 5 with two projects:

    • Node, for logic and permissions;
    • Browser Mode (@vitest/browser-playwright + vitest-browser-vue), for components.

    MSW 3 handlers are shared with a thin Playwright E2E suite (F36-F39). The platform owns end-to-end tests across the whole ecosystem (F34). @vue/test-utils with happy-dom is faster but less faithful (F37). Avoid Playwright component testing (F38).

    Server and headers (F41-F48). nginx-unprivileged alpine-slim, read-only root filesystem.

    • Caching:
      • /assets/: try_files $uri =404 and public, max-age=31536000, immutable.
      • /: try_files $uri /index.html and no-cache.
    • Headers on every location (one include, or add_header_inherit merge, with always):
      • Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self'; connect-src 'self' https://api.rsl-commerce.test wss://api.rsl-commerce.test; base-uri 'none'; form-action 'self'; frame-ancestors 'none'; object-src 'none';
      • nosniff, strict-origin-when-cross-origin, COOP same-origin, a deny-all Permissions-Policy, and X-Robots-Tag: noindex, nofollow.
    • Also: server_tokens off and build.assetsInlineLimit: 0. The API and WebSocket origins come from runtime config. HSTS is set at the platform proxy.

    Risks and unknowns

    • Brand-new majors. TanStack Table 9 (2026-08-04), Pinia 4, Vitest 5, VueUse 15 and MSW 3 (2026-09-28) are all fresh. Integrations lag: Hey API's MSW plugin targets MSW v2 and is "in-progress", and its Colada plugin is documented for v0 (F17). Fall back to TanStack Query if Colada codegen breaks.
    • SDK ownership. Who generates the SDK (backend or each app), and whether it includes validators and query helpers, is a contract question (AGENTS.md:12). The backend spike decides it.
    • Shared same-site hosts. Customers and staff share the cookie jar on api.rsl-commerce.test, and an XSS on any sibling host is same-site. The backend must isolate admin endpoints: a separate cookie name, per-route CORS and Origin checks, or an admin-only API host or path.
    • style-src 'self' is unproven. The design system has no code yet. Start with Report-Only, and allow a looser policy for the dev server.
    • Timeout values. OWASP suggests 2-5 min idle for high-value apps, and NIST AAL2 allows up to 1 h (F28, F29). The values above are proposals.
    • The WebSocket contract is unspecified: URL, authentication, per-role filtering and resume behaviour (F34, F35).
    • Research gaps.
      • Some lookups were rate-limited (HTTP 429), and tanstack.com returned 500, so the TanStack docs were read from the repo at a pinned commit.
      • The Nuxt inline-script finding comes from reading its source, not from a build. The checkout spike's experiment confirms it independently (commerce-checkout#1, F16-F17).

    What this means for the plan

    Candidate decisions for the admin ADR:

    • D1: a Vue 3.5 + Vite 8 SPA with Vue Router 5 typed routing, and no Nuxt.
    • D2: TanStack Table 9 + Virtual behind DataTable; TanStack Form + Zod 4.
    • D3: Pinia 4 + Pinia Colada (or TanStack Query), with no persisted server data.
    • D4: ECharts 6 via vue-echarts.
    • D5: a cookie-only session; RBAC mirrored from /me; no CASL.
    • D6: useWebSocket with backoff, jitter, a heartbeat and a refetch on reconnect.
    • D7: Vitest 5 (Node + Browser Mode), MSW 3 and Playwright E2E.
    • D8: nginx-unprivileged, non-root, with a read-only root, the headers above and runtime config.

    Testable requirements:

    • No credentials in Web Storage.
    • No unsafe-inline or unsafe-eval in the CSP, and zero violations in E2E.
    • Every route declares a permission.
    • Sensitive actions send an Idempotency-Key and handle reauth_required.
    • Cache headers are as specified.

    The backend must provide:

    • GET /me, with roles, flat permissions, the idle and absolute expiry, and the last-authentication time.
    • The cookie spec, the CSRF mechanism and the CORS allowlist.
    • Distinct problem types for 401, 403 and reauth_required, and a step-up endpoint.
    • Idempotency-Key support and an audit record for every sensitive action.
    • Cursor-paginated audit lists with stable ordering.
    • The WebSocket details: URL, cookie auth plus an Origin allowlist, per-role filtering, event IDs, closing when the session expires, and a resync signal.
    • Stable OpenAPI operationIds and tags.

    The platform must provide routing from admin.rsl-commerce.test to port 8080, TLS and HSTS, and runtime API and WebSocket URLs.

    Verified by the lead before posting:

    • npm view gives vue-router 5.3.1, pinia 4.0.3, @tanstack/vue-table 9.2.6, @tanstack/vue-form 1.33.5, zod 4.6.5, echarts 6.1.0, vue-echarts 8.3.1, chart.js 4.5.1, vitest 5.0.3, msw 3.0.2, @vueuse/core 15.0.0 and @hey-api/openapi-ts 0.99.0;
    • unplugin-vue-router is deprecated with "Merged into vuejs/router";
    • @vee-validate/zod 4.15.1 peers zod ^3.24.0;
    • vitest needs Node ^22.12.0 || ^24.0.0 || >=26.0.0;
    • the Playwright v1.63.0 release notes say the experimental CT packages "will no longer be updated".
    Evidence

    RQ-1: Framework

    • F1 [verified-code] Scope: products, orders, refunds, subscriptions, campaigns, roles, audit and dashboards (admin/README.md:8-16 @ a89f48b). The admin owns no components (:21) and uses the SDK plus WebSocket (:26-27).
    • F2 [verified-code] The design system supplies components, tokens and the shared TS and lint config (design-system/README.md:9-16 @ 3e8fd6e), and holds "no application-specific components" (design-system/AGENTS.md:8-9 @ 3e8fd6e).
    • F3 [verified-code] "Every technology must solve a real problem in the system". (AGENTS.md:29-30 @ d0573a0)
    • F4 [sourced] Versions: vue 3.5.43, vite 8.3.3, vue-router 5.3.1, nuxt 4.6.0. unplugin-vue-router is deprecated: "Merged into vuejs/router". Vite 8 "ships with Rolldown as its single, unified, Rust-based bundler". (npm unplugin-vue-router; Vite 8, accessed 2026-10-07)
    • F5 [sourced] Vue Router 5 "merges unplugin-vue-router (file-based routing) into the core package": the vue-router/vite plugin plus typed-router.d.ts. Data loaders are still experimental. (v4→v5 migration, accessed 2026-10-07, v5)
    • F6 [sourced] With ssr: false, Nuxt emits index.html, 200.html and 404.html. nuxt@4.6.0 has 66 direct dependencies, among them the nitro server, devtools and telemetry. (Nuxt rendering; npm nuxt, accessed 2026-10-07, 4.6.0)
    • F7 [sourced] Nuxt collects anonymous telemetry unless you opt out, and auto-imports components, composables and Vue APIs. (auto-imports; nuxt/telemetry, accessed 2026-10-07)
    • F8 [sourced] Nuxt's renderer emits inline __NUXT_DATA__ and window.__NUXT__.config= scripts, and nuxt-security allows them by hash. (payload.ts @85b8d54 L22-49; nuxt-security, accessed 2026-10-07)
    • F9 [sourced] Vite's cspNonce needs per-request replacement. Small assets are inlined as data: URLs (controlled by assetsInlineLimit), and data: must never be allowed in script-src. (Vite CSP; build options, accessed 2026-10-07, 8.3)
    • F10 [inference] F5 gives a plain SPA Nuxt's routing advantage. F6-F8 add tooling, telemetry, implicit imports and inline scripts that an admin without SSR doesn't need (F3).

    RQ-2: Libraries

    • F11 [sourced] @tanstack/vue-table 9.2.6 (9.0.0 shipped 2026-08-04) is headless: "100% control over the markup and styles". Virtualization comes from @tanstack/vue-virtual 3.13.39. (quick start; virtualization, accessed 2026-10-07, v9)
    • F12 [sourced] With manualPagination, "If you don't know the page count, pass -1". A cursor recipe uses useInfiniteQuery and nextCursor. (pagination, accessed 2026-10-07, v9)
    • F13 [sourced] VeeValidate's stable release is 4.15.1 (2025-06-07), and @vee-validate/zod peers zod ^3.24. Standard Schema support exists only in v5.0.0-beta.0 (2025-08-02). (v5 beta; npm, accessed 2026-10-07)
    • F14 [sourced] TanStack Form "natively supports all libraries following the Standard Schema specification". (validation, accessed 2026-10-07, Vue adapter 1.33.5)
    • F15 [sourced] FormKit "outputs an opinionated accessible DOM structure" and ships themes. (FormKit, accessed 2026-10-07, 2.1.2)
    • F16 [sourced] Standard Schema is at v1. Gzipped sizes: zod 4.6.5 is 88.6 kB and valibot 1.5.0 is 14.7 kB. (Standard Schema; Bundlephobia, accessed 2026-10-07)
    • F17 [sourced] Hey API 0.99.0 is "in initial development. Please pin an exact version". It has Zod v4 and Valibot v1 plugins. Its Colada plugin is documented for "v0", and its MSW plugin is "in-progress" and supports v2. (Hey API docs @d6ba11f, accessed 2026-10-07)
    • F18 [sourced] orval 8.40.0 emits Zod 4 alongside query clients and mocks. (orval Zod, accessed 2026-10-07)
    • F19 [sourced] ECharts 6.1.0's aria generates an aria-label and decals, and it tree-shakes through echarts/core. vue-echarts 8.3.1 injects its styles through CSSStyleSheet(), so it works under a strict CSP. (ECharts aria; vue-echarts, accessed 2026-10-07)
    • F20 [sourced] Chart.js: "it is up to the user to create the canvas element in a way that is accessible". Its latest release is 4.5.1 (2025-10-13). (Chart.js accessibility, accessed 2026-10-07)
    • F21 [sourced] Pinia 4.0.0 is "ESM only". Pinia Colada 1.4.7 is 5.2 kB gzipped and has useInfiniteQuery; @tanstack/vue-query 5.104.1 is 14.1 kB. (Pinia v4; Colada vs TanStack, accessed 2026-10-07)
    • F22 [sourced] Every candidate repo had a push in the last three weeks, except formkit (2026-07-24) and vee-validate (2026-03-04). None is archived. (GitHub API, accessed 2026-10-07)
    • F23 [inference] Headless libraries render design-system components (F2, F11, F14), and FormKit competes with them (F15). VeeValidate lags on Zod 4 (F13, F22). ECharts' accessibility outweighs its size (F19, F20).

    RQ-3: Session and permissions

    • F24 [verified-code] The backend is authoritative for authorization (AGENTS.md:21-22 @ d0573a0). "Hiding a button is not authorization", and sensitive actions go through audited operations (admin/AGENTS.md:8-9,15-17 @ a89f48b).
    • F25 [verified-code] The hosts are rsl-commerce.test and its api., admin., checkout. and docs. subdomains. (README.md:20-26 @ d0573a0)
    • F26 [sourced] A site is a public-suffix entry plus one label; test is not on the 2026-10-06 list, so * applies. (MDN Site; PSL, accessed 2026-10-07)
    • F27 [sourced] SameSite "does not replace a proper CSRF defense", and sibling subdomains are same-site. The cheat sheet recommends custom headers that force a preflight, an allowlist of origins and Origin checks. (OWASP CSRF, accessed 2026-10-07)
    • F28 [sourced] __Host- with Secure; HttpOnly; SameSite=Strict; Path=/. "Do not store … session IDs … in localStorage or sessionStorage". For high-value apps: 2-5 min idle and 4-8 h absolute, enforced server-side. (OWASP Session, accessed 2026-10-07)
    • F29 [sourced] OWASP: re-authenticate "before sensitive transactions". NIST 800-63B-4: session secrets "SHOULD NOT be placed in … HTML5 Local Storage"; AAL2 allows 1 h idle and 24 h overall. (OWASP Authn; NIST AAL, accessed 2026-10-07)
    • F30 [sourced] Route meta is checked in beforeEach and typed by augmenting RouteMeta. (Route Meta, accessed 2026-10-07, v5)
    • F31 [sourced] @casl/vue 3.0.1 provides abilitiesPlugin, useAbility and <Can>. (casl-vue, accessed 2026-10-07)
    • F32 [verified-code] The backend README names no language. (backend/README.md:1-20 @ 38a8548)
    • F33 [inference] All the hosts are same-site (F25, F26), so neither SameSite nor Sec-Fetch-Site isolates the admin (F27); that leaves an Origin allowlist plus a header or token. A flat permission set is the least code that mirrors RBAC (F24, F30). CASL needs CASL rules from the backend (F31, F32).

    RQ-4: Real-time, audit views, tests

    • F34 [verified-code] Alerts cover orders, failed payments, jobs, inventory and past-due subscriptions (admin/README.md:15-16 @ a89f48b). The backend authorizes events into browser-safe ones (backend/AGENTS.md:13-14 @ 38a8548). The platform owns end-to-end tests across the ecosystem (platform/README.md:17 @ 5db9dab).
    • F35 [sourced] VueUse 15's useWebSocket has autoReconnect (a number or a function), heartbeat and status; useIdle tracks inactivity. OWASP: "Validate the Origin header on every handshake", close on session expiry, and keep tokens out of URLs. (useWebSocket; OWASP WebSocket, accessed 2026-10-07)
    • F36 [sourced] Vitest 4 removed Browser Mode's experimental tag. Vitest 5 requires Node 22, and the docs recommend Playwright with vitest-browser-vue. (Vitest 4; Browser Mode; v5.0.0, accessed 2026-10-07)
    • F37 [sourced] Vue's testing guide recommends Vitest, @vue/test-utils and happy-dom. Browser runners catch "style issues, real native DOM events…" but are slower. (Vue testing, accessed 2026-10-07)
    • F38 [sourced] Playwright 1.63.0: the experimental-ct-vue packages "will no longer be updated"; they move to a stories model. (v1.63.0, 2026-09-04, accessed 2026-10-07)
    • F39 [sourced] MSW 3.0.0 (2026-09-28) is ESM-only, needs Node 22 or later, and works in Browser Mode through a test.extend fixture. (v3.0.0; recipe, accessed 2026-10-07)
    • F40 [inference] One library covers backoff, the heartbeat and the idle timer (F35). Nothing replays missed events, so the client refetches after reconnecting. Browser Mode is stable (F36), and Playwright component testing is a dead end (F38).

    RQ-5: Serving

    • F41 [verified-code] The platform owns the proxy, TLS and routing (platform/README.md:12 @ 5db9dab), and provides the config each app declares (AGENTS.md:14 @ d0573a0).
    • F42 [sourced] nginx-unprivileged runs as UID 101 on port 8080, with its pid and temp files in /tmp. It is rebuilt on each release, on critical CVEs and weekly. The 1.30.5 alpine-slim image is about 8 MB compressed. (README; Docker Hub tags, accessed 2026-10-07)
    • F43 [sourced] add_header is inherited "if and only if there are no add_header directives defined on the current level". add_header_inherit merge (1.29.3+) changes that, and server_tokens defaults to on. (headers module, accessed 2026-10-07)
    • F44 [sourced] The Caddy 2.11 Alpine image has no USER, so it runs as root, and it exposes port 2019 for its admin API. admin off and auto_https off are needed. (Dockerfile; options, accessed 2026-10-07)
    • F45 [sourced] static-web-server 2.44.0 is a "~4 MB static binary" with an SPA fallback. (README, accessed 2026-10-07)
    • F46 [sourced] OWASP recommends frame-ancestors, nosniff, strict-origin-when-cross-origin, COOP, Permissions-Policy and X-Robots-Tag: noindex. A <meta> CSP can't set frame-ancestors. (Headers; CSP, accessed 2026-10-07)
    • F47 [sourced] Hashed assets get max-age=31536000, immutable; HTML gets no-cache. (MDN Cache-Control, accessed 2026-10-07)
    • F48 [inference] Vite's output contains only external hashed files (F9), so a 'self' CSP works without nonces. The checkout spike's experiment showed a Vite build rendering under script-src 'self' (commerce-checkout#1, F17). nginx-unprivileged is non-root, small and patched (F42), while Caddy needs three changes (F44). nginx's trap is header inheritance (F43).

    Current state

    The repo is docs-only:

    • admin/README.md and admin/AGENTS.md;
    • CodeQL on actions (.github/workflows/codeql.yml:24);
    • Dependabot for actions only (.github/dependabot.yml:8).

    Sources

    All accessed 2026-10-07:

    No experiments were needed for this spike, and no repository files changed.

    workflow · claude-code · 2026-10-07 00:39

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    stage:doneDonetrack:fullNeeds evidence: brief, research, plan, execute, reviewtype:spikeResearch that answers a question and ends there

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions