v0.2 Phase 0+1: planning doc + tooling baseline
Phase 0 — docs/release/v0.2-frontend-overhaul.md as single source of truth for the v0.2 frontend coherence pass. Records hard rules, tooling pins, sacred-behaviour contract list, wrapper catalogue, per-page migration order, verification matrix, KI-05 plan, and the explicit "DO NOT add Skeleton" line. Phase 1 — tooling baseline. All exact-pinned per the plan: - @playwright/test@1.60.0 + playwright@1.60.0 + @axe-core/playwright@4.11.3 - rollup-plugin-visualizer@7.0.1 (wired behind ANALYZE=1) - @vitest/browser@4.1.6 + @vitest/browser-playwright@4.1.6 (provider) - vitest-browser-svelte@2.1.1 (runes-aware Svelte 5 bridge) - cargo-nextest installed globally New configs: playwright.config.ts (frontend-only, dev:frontend webServer, 900x700 + 1440x900 projects, visual baselines deferred), vitest.browser.config.js (separate from jsdom suite). New scripts: test:e2e, test:e2e:ui, test:browser, analyze, test:rust:fast, guard:no-skeleton. guard-no-skeleton.mjs walks package.json + package-lock + src/ for any @skeletonlabs reference and exits 1 if found — locks in the no-Skeleton hard rule for any future agent. Smoke baseline (tests/e2e/smoke.spec.ts): 16 tests passing across two viewports — app loads without Tauri runtime, keyboard nav, axe scan (color-contrast deferred to Phase 7 per docs/release/v0.1-contrast-audit.md), light/dark theme cycle, all three sensory zones (cave/energy/reset). 10 screenshots emitted to test-results/ as non-failing artefacts. Surfaced + fixed two browser-preview bugs while landing the baseline: - src/lib/utils/osInfo.ts: FALLBACK_BROWSER_INFO was evaluated at SSR module-load (navigator undefined → os: 'unknown'), and the UA check ran before navigator.platform — so Playwright's Windows-UA Chromium on a Linux runner detected as Windows, useCustomChrome went true, Titlebar mounted and tripped Tauri-only APIs. Now lazy-built per call; platform is the primary signal, UA only a fallback. - src/lib/components/Titlebar.svelte: defensive hasTauriRuntime() guard on every handler and the $effect. Titlebar should not crash if any future code path mounts it without Tauri. .gitignore: reports/, .playwright/, test-results/, playwright-report/. Per-phase gate green: npm run check (0/0), npm test (0/0), npm run test:e2e (16/16), npm run guard:no-skeleton (clean). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -8,6 +8,12 @@ dist/
|
|||||||
.cargo/
|
.cargo/
|
||||||
.lumotia-last-audit
|
.lumotia-last-audit
|
||||||
|
|
||||||
|
# v0.2 frontend tooling artefacts
|
||||||
|
reports/
|
||||||
|
.playwright/
|
||||||
|
test-results/
|
||||||
|
playwright-report/
|
||||||
|
|
||||||
# Python bytecode (from release scripts under scripts/)
|
# Python bytecode (from release scripts under scripts/)
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.pyc
|
*.pyc
|
||||||
|
|||||||
261
docs/release/v0.2-frontend-overhaul.md
Normal file
261
docs/release/v0.2-frontend-overhaul.md
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
---
|
||||||
|
name: v0.2-frontend-overhaul
|
||||||
|
type: release
|
||||||
|
tags: [release, v0.2, frontend, coherence-pass, no-skeleton, bits-ui, formsnap]
|
||||||
|
description: "v0.2 frontend coherence pass. NOT a redesign. Lands one button grammar, one status grammar, one notice/error grammar, one settings-row grammar across every page without disturbing the brand, identity surfaces, or token system. Tooling-first: Playwright + axe + @vitest/browser + rollup-plugin-visualizer + cargo-nextest installed before any UI work. Long-lived feat/v0.2-frontend-overhaul branch; main keeps shipping v0.1.x bugfixes. Ships as v0.2."
|
||||||
|
---
|
||||||
|
|
||||||
|
# Lumotia v0.2 — frontend overhaul (coherence pass, tooling-first)
|
||||||
|
|
||||||
|
**Source of truth.** All implementation, gate, and decision detail for the v0.2 frontend overhaul lives in this single file. Per-page status, resolved tooling pins, sacred-behaviour test list, wrapper catalogue, and verification matrix are all tracked here.
|
||||||
|
|
||||||
|
## 1. Why — coherence pass, not redesign
|
||||||
|
|
||||||
|
The current UI is already ~85–90% cohesive: token-driven warm-amber dark system, named shadow scale, 5 bundled fonts, sensory-zone CSS, per-region accessibility controls. The gap is **grammar inconsistency** — one button style here, another there; one notice pattern in Files, a different one in History; ~95 ad-hoc form controls in `SettingsPage`. The fix is to land one button grammar, one status grammar, one notice/error grammar, one settings-row grammar — across every page — **without disturbing the brand or the bespoke identity surfaces.**
|
||||||
|
|
||||||
|
**Aesthetic target.** Warm brutalist notebook cockpit. Calm, local, tactile, low-noise. Not SaaS, not garden-game, not generic Tailwind, not a wholesale sage rebrand.
|
||||||
|
|
||||||
|
**Tooling is Phase 1, not an afterthought.** Verification and performance tooling are installed before any UI work. If a tool is needed, install/configure it instead of simulating the check manually.
|
||||||
|
|
||||||
|
## 2. Hard rules
|
||||||
|
|
||||||
|
1. **Do not install Skeleton.**
|
||||||
|
2. **Do not replace Lumotia's token system or brand direction.** Amber/copper stays primary `--color-accent`; sage/moss enters as an optional support token only.
|
||||||
|
3. **Do not rewrite bespoke identity surfaces:** recording controls, waveform/timer, bionic transcript surfaces, FocusTimer, MicroSteps, TaskSidebar, ModelDownloader, HotkeyRecorder, ZonePicker.
|
||||||
|
4. **Use exact-pinned packages.**
|
||||||
|
5. **If a tool is needed, install/configure it instead of simulating the check manually.**
|
||||||
|
|
||||||
|
## 3. Tooling baseline (resolved pins)
|
||||||
|
|
||||||
|
Installed in Phase 1 of this overhaul. Pinned exactly via `--save-exact`.
|
||||||
|
|
||||||
|
| Package | Version | Role |
|
||||||
|
|---|---|---|
|
||||||
|
| `@playwright/test` | 1.60.0 | E2E test runner |
|
||||||
|
| `playwright` | 1.60.0 | Vitest browser-mode peer dep |
|
||||||
|
| `@axe-core/playwright` | 4.11.3 | Accessibility scan inside Playwright |
|
||||||
|
| `rollup-plugin-visualizer` | 7.0.1 | Bundle-size analyser (ANALYZE=1 vite build) |
|
||||||
|
| `@vitest/browser` | 4.1.6 | Vitest browser-mode core |
|
||||||
|
| `@vitest/browser-playwright` | 4.1.6 | Vitest browser-mode provider |
|
||||||
|
| `vitest-browser-svelte` | 2.1.1 | Svelte 5 runes-aware bridge |
|
||||||
|
| `cargo-nextest` | latest (cargo install) | Fast Rust test runner |
|
||||||
|
| `bits-ui` | 2.18.1 | Headless Svelte 5 primitives |
|
||||||
|
| `formsnap` | 2.0.1 | Form field + label + error wrapper |
|
||||||
|
| `sveltekit-superforms` | 2.30.1 | Form state + validation |
|
||||||
|
| `zod` | 4.4.3 | Schema validation |
|
||||||
|
| `@internationalized/date` | 3.12.1 | Bits UI peer dep |
|
||||||
|
|
||||||
|
`npm run` scripts added: `test:e2e`, `test:e2e:ui`, `test:browser`, `analyze`, `test:rust:fast`, `guard:no-skeleton`.
|
||||||
|
|
||||||
|
Vite plugin: `rollup-plugin-visualizer` registered behind `ANALYZE=1` env flag in `vite.config.js`; emits `reports/bundle-stats.html`.
|
||||||
|
|
||||||
|
Tauri-IPC acceptance rule: frontend-only Playwright tests must not require Tauri IPC. Any Tauri-only feature gets either a graceful browser-preview fallback (mock the invoke boundary in dev) or is skipped in Playwright with a documented reason and verified manually + by `cargo test` instead.
|
||||||
|
|
||||||
|
## 4. Stack additions vs. existing
|
||||||
|
|
||||||
|
Additive only. No replacements.
|
||||||
|
|
||||||
|
- Existing: Svelte 5.53.12, Vite 6.4.2, SvelteKit 2.58.0, Tauri 2.10.1, Tailwind 4.2.1, vitest 4.1.6 (jsdom).
|
||||||
|
- Added: Playwright + axe-core (E2E), `@vitest/browser` + `vitest-browser-svelte` (component-mode browser tests), bits-ui + formsnap + superforms + zod (headless primitives + form layer), rollup-plugin-visualizer (build-time only), cargo-nextest (Rust test runner).
|
||||||
|
|
||||||
|
Superforms runs client-side only (Tauri uses static adapter; no SvelteKit server actions). Zod runs in-process. Bits UI's Floating-UI portals target `document.body`; the `/preview` window uses **zero** portaled primitives to keep its `WindowTypeHint::Utility` DOM flat.
|
||||||
|
|
||||||
|
## 5. Sacred behaviours (contract tests)
|
||||||
|
|
||||||
|
Each becomes a Playwright or `@vitest/browser` test. Code may move during the overhaul (notably during Phase 6's shell split), but the **behaviour must remain verbatim**.
|
||||||
|
|
||||||
|
| # | Behaviour | Source today | Moves to | Test approach |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 1 | Recording-state nav fade | `src/lib/Sidebar.svelte:108-157` | `AppChrome.svelte` | Playwright: simulate recording, screenshot + ARIA assert + axe scan |
|
||||||
|
| 2 | Global hotkey + 120ms debounce | `+layout.svelte:70-198` | `AppRuntime.svelte` | Browser-mode covers frontend debounce (via extracted `hotkeyDebounce.ts`); OS registration covered by `cargo test` |
|
||||||
|
| 3 | `+layout@.svelte` secondary windows skip shell | `routes/{float,viewer,preview}/+layout@.svelte` | unchanged | Playwright: navigate to `/float`, assert no sidebar/titlebar |
|
||||||
|
| 4 | Cross-window preference sync (`PREFERENCES_CHANGED_EVENT`) | `+layout.svelte:296-305` | `AppRuntime.svelte` | Browser-mode: emit event, assert listener fires |
|
||||||
|
| 5 | 5-font system (woff2 bundled, font-family wiring) | `src/app.css:9-47` | unchanged | Playwright: cycle all 5 fonts, screenshot transcript surface each |
|
||||||
|
| 6 | Bionic reading mode | `src/lib/actions/bionicReading.ts` | unchanged | Component test: action mounts, transforms text |
|
||||||
|
| 7 | Per-region accessibility controls (`--font-size-body`, `--letter-spacing-body`, `--line-height-body`) | `src/lib/utils/accessibilityTypography.ts` | unchanged | Component test: change pref, assert CSS vars on root |
|
||||||
|
| 8 | `prefers-reduced-motion` | `src/app.css:510-530` + `data-reduce-motion="true"` | unchanged | Playwright: set `prefers-reduced-motion: reduce`, assert no fade transitions |
|
||||||
|
| 9 | Sensory-zone tinting (cave/energy/reset) | `:root[data-zone="…"]` overrides | unchanged | Playwright: cycle 3 zones × 2 themes = 6 surface sets |
|
||||||
|
| 10 | Sidebar hotkeys (`[`, `Ctrl+K`, `Ctrl+,`) | `+layout.svelte` handleKeydown | `AppRuntime.svelte` | Browser-mode: press each, assert state change |
|
||||||
|
|
||||||
|
## 6. Wrapper catalogue
|
||||||
|
|
||||||
|
Filled progressively during Phases 4–5. Wrappers live in `src/lib/ui/`; bespoke identity surfaces stay in `src/lib/components/`.
|
||||||
|
|
||||||
|
### 6.1 Phase 4 alias wrappers (thin, same props)
|
||||||
|
|
||||||
|
| Wrapper | Wraps | Status |
|
||||||
|
|---|---|---|
|
||||||
|
| `LumotiaCard` | `Card.svelte` | pending |
|
||||||
|
| `LumotiaStatusPill` | `StatusPill.svelte` | pending |
|
||||||
|
| `LumotiaToggle` | `Toggle.svelte` | pending |
|
||||||
|
| `LumotiaSettingsGroup` | `SettingsGroup.svelte` | pending |
|
||||||
|
| `LumotiaEmptyState` | `EmptyState.svelte` | pending |
|
||||||
|
| `LumotiaPostCaptureCard` | `PostCaptureCard.svelte` | pending |
|
||||||
|
|
||||||
|
### 6.2 Phase 5 new primitives
|
||||||
|
|
||||||
|
| Wrapper | Implementation | Status |
|
||||||
|
|---|---|---|
|
||||||
|
| `LumotiaButton` | native + `.btn-*` class system; primary / secondary / tertiary / destructive | pending |
|
||||||
|
| `LumotiaIconButton` | native + lucide-svelte; standard sizing + tooltip slot | pending |
|
||||||
|
| `LumotiaNotice` | custom; info / caution / danger / success inline notices | pending |
|
||||||
|
| `LumotiaProgress` | native `<progress>` + tokens; fall back to `role="progressbar"` only if native styling proves inconsistent | pending |
|
||||||
|
| `LumotiaField` | native + Formsnap Field integration; text + textarea + label + error wrapper | pending |
|
||||||
|
| `LumotiaSelect` | Bits UI Select | pending |
|
||||||
|
| `LumotiaCombobox` | Bits UI Combobox | pending |
|
||||||
|
| `LumotiaDialog` | Bits UI Dialog | pending |
|
||||||
|
| `LumotiaTabs` | Bits UI Tabs | pending |
|
||||||
|
| `LumotiaTooltip` | Bits UI Tooltip | pending |
|
||||||
|
| `LumotiaMenu` / `LumotiaPopover` | Bits UI DropdownMenu / Popover | pending |
|
||||||
|
|
||||||
|
### 6.3 Bespoke surfaces — DO NOT wrap, DO NOT rewrite
|
||||||
|
|
||||||
|
These are Lumotia's identity. Pages import them; they stay outside `src/lib/ui/`:
|
||||||
|
|
||||||
|
- Record controls + recording-state UI on `DictationPage`
|
||||||
|
- Waveform / `VisualTimer` SVG ring
|
||||||
|
- Bionic transcript surfaces (`use:bionic` action consumers)
|
||||||
|
- `FocusTimer`
|
||||||
|
- `MicroSteps`
|
||||||
|
- `TaskSidebar`
|
||||||
|
- `ModelDownloader`
|
||||||
|
- `HotkeyRecorder`
|
||||||
|
- `ZonePicker`
|
||||||
|
- `CompletionSparkline`
|
||||||
|
- `EnergyChip`, `LlmStatusChip`, `SpeakerButton`, `UnicodeSpinner`, `ResizeHandles`
|
||||||
|
|
||||||
|
If a page using one of these needs grammar normalisation, normalise the surrounding chrome (buttons, notices, cards), not the identity component.
|
||||||
|
|
||||||
|
## 7. Per-page migration checklist
|
||||||
|
|
||||||
|
Order: smallest → riskiest. Dictation **before** Settings so DictationPage sets the grammar Settings inherits.
|
||||||
|
|
||||||
|
| # | Page | LOC | Notes | Status |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 1 | `ShutdownRitualPage` | 169 | First smoke test | pending |
|
||||||
|
| 2 | `FilesPage` | 286 | Bank LumotiaField + LumotiaNotice patterns | pending |
|
||||||
|
| 3 | `FirstRunPage` | 461 | Formsnap proof point; LumotiaTabs stepper | pending |
|
||||||
|
| 4 | `TasksPage` | 726 | Wrap `WipTaskList` / `TaskSidebar` / `MicroSteps`, don't rewrite | pending |
|
||||||
|
| 5 | `HistoryPage` | 1 225 | FTS5 search → LumotiaCombobox; row patterns standardised; row actions via LumotiaMenu | pending |
|
||||||
|
| 6 | `DictationPage` | 1 263 | **Centrepiece**. Recording state machine + post-capture card + hotkey wiring stay verbatim; only surrounding chrome migrates | pending |
|
||||||
|
| 7 | `SettingsPage` | 2 791 | **Last**. Section by section. Existing IA stays. Formsnap only where validation/error semantics matter | pending |
|
||||||
|
| 8 | `/float` window | — | TaskList in frameless window | pending |
|
||||||
|
| 9 | `/viewer` window | — | Bionic-reading action + font tokens load-bearing | pending |
|
||||||
|
| 10 | `/preview` window | — | Wayland-hardened; zero portaled primitives | pending |
|
||||||
|
|
||||||
|
After **each** page migration, run the full per-page gate:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run check
|
||||||
|
npm run test
|
||||||
|
npm run test:browser
|
||||||
|
npm run test:e2e
|
||||||
|
npm run test:rust:fast
|
||||||
|
```
|
||||||
|
|
||||||
|
Per-page commit shape: page end-to-end + dead components deleted (if no remaining consumers) + the gate above green + `design-system-v2` preview updated if the page exposed a new wrapper pattern.
|
||||||
|
|
||||||
|
## 8. Phase 8 verification matrix (before squash-merge)
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo fmt --check
|
||||||
|
cargo clippy --workspace --all-targets -- -D warnings
|
||||||
|
cargo test --workspace
|
||||||
|
cargo nextest run --workspace
|
||||||
|
npm run check
|
||||||
|
npm run test
|
||||||
|
npm run test:browser
|
||||||
|
npm run test:e2e
|
||||||
|
npm run analyze # emits reports/bundle-stats.html
|
||||||
|
scripts/dogfood-rebrand-drill.sh
|
||||||
|
npm run guard:no-skeleton
|
||||||
|
rg "from ['\"]\$lib/components" src/lib/pages src/routes # informational only
|
||||||
|
```
|
||||||
|
|
||||||
|
`guard:no-skeleton` fails loudly if `@skeletonlabs` appears in `package.json`, `package-lock.json`, or anywhere under `src/`. Component-import `rg` is informational only at this stage: a future small script can allowlist exact paths and make this guard failing once the bespoke list is fully settled.
|
||||||
|
|
||||||
|
Cross-platform CI green — `.github/workflows/check.yml` + `build.yml` pass on Linux/macOS/Windows.
|
||||||
|
|
||||||
|
Visual baseline approval is **deferred**: after the UI stabilises and Jake approves screenshots, a separate follow-up commit promotes the baselines and only then do Playwright visual regressions fail the build.
|
||||||
|
|
||||||
|
## 9. KI-05 resolution
|
||||||
|
|
||||||
|
Drop `settings.theme` writes; `preferences.theme` is canonical. Resolved in the same commit as Phase 3 semantic-alias token additions. Touches:
|
||||||
|
|
||||||
|
- `src/lib/stores/page.svelte.ts` — drop `theme` from `SettingsState`
|
||||||
|
- `src/lib/pages/SettingsPage.svelte:1118`, `:2360` — repoint bindings to `preferences.theme` with mapped options
|
||||||
|
- `src/routes/+layout.svelte:61-68` and the three `+layout@.svelte` — retire the migration `$effect`
|
||||||
|
- One-shot localStorage migration that copies any historical `settings.theme` into `preferences` on first run after the cleanup
|
||||||
|
|
||||||
|
## 10. Aesthetic direction — warm brutalist notebook cockpit
|
||||||
|
|
||||||
|
Calm, local, tactile, low-noise. Warm amber/copper accents; charcoal/cream surfaces; named shadow scale; bundled woff2 typography. Generous whitespace, deliberate corners, no SaaS gloss. Every page migration is reviewed against this vibe; if a page starts feeling SaaS-bland during a wrapper sweep, stop and re-grain.
|
||||||
|
|
||||||
|
## 11. Phase log
|
||||||
|
|
||||||
|
Filled during execution. Each entry: phase #, date, what landed, what's next.
|
||||||
|
|
||||||
|
| Phase | Status | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| 0 | ✅ complete | Doc written; tooling pins recorded |
|
||||||
|
| 1 | ✅ complete | Tooling baseline green: `npm run check` clean, `npm test` clean, `npm run test:e2e` 16/16, `npm run guard:no-skeleton` clean, 10 screenshot artefacts. Browser-preview OS detection fixed (see Regression diary). |
|
||||||
|
| 2 | pending | UI/form deps |
|
||||||
|
| 3 | pending | Additive tokens + KI-05 |
|
||||||
|
| 4 | pending | Wrapper alias layer |
|
||||||
|
| 5 | pending | New primitives + design-system-v2 preview |
|
||||||
|
| 6 | pending | Shell split |
|
||||||
|
| 7.1 ShutdownRitualPage | pending | |
|
||||||
|
| 7.2 FilesPage | pending | |
|
||||||
|
| 7.3 FirstRunPage | pending | |
|
||||||
|
| 7.4 TasksPage | pending | |
|
||||||
|
| 7.5 HistoryPage | pending | |
|
||||||
|
| 7.6 DictationPage | pending | |
|
||||||
|
| 7.7 SettingsPage | pending | |
|
||||||
|
| 7.8 /float | pending | |
|
||||||
|
| 7.9 /viewer | pending | |
|
||||||
|
| 7.10 /preview | pending | |
|
||||||
|
| 8 | pending | Full release gate |
|
||||||
|
|
||||||
|
## 12. Bundle-size delta
|
||||||
|
|
||||||
|
To be filled after Phase 8 from `reports/bundle-stats.html`. Compare against pre-overhaul baseline captured in Phase 1.
|
||||||
|
|
||||||
|
## 13. Regression diary
|
||||||
|
|
||||||
|
Anything that broke during a migration and how it was fixed. Appended chronologically.
|
||||||
|
|
||||||
|
### Phase 1 — browser-preview OS misdetection (Titlebar crash)
|
||||||
|
|
||||||
|
**Symptom.** Playwright smoke test `app loads without Tauri runtime` failed with `Cannot read properties of undefined (reading 'metadata') in $effect in Titlebar.svelte`.
|
||||||
|
|
||||||
|
**Root cause (two compounding bugs):**
|
||||||
|
1. `src/lib/utils/osInfo.ts` defined `FALLBACK_BROWSER_INFO` at module-load time. Under SvelteKit SSR, `navigator` is undefined, so `detectBrowserOs()` froze at `'unknown'` and `isLinux()` returned false even on Linux browsers.
|
||||||
|
2. Inside `detectBrowserOs()`, the UA was consulted before `navigator.platform`. Playwright's Chromium ships with a **Windows** UA on Linux runners (`Mozilla/5.0 (Windows NT 10.0; Win64; x64)`), so the UA check returned `'windows'` first, overriding the real `Linux x86_64` platform string.
|
||||||
|
|
||||||
|
**Fix.**
|
||||||
|
- `osInfo.ts` — replace the module-level constant with a lazy `buildBrowserFallback()` that runs at call-time. Re-order `detectBrowserOs()` to read `navigator.platform` first (the truthful OS surface, immune to UA spoofing) and only fall back to UA when platform is unknown.
|
||||||
|
- `Titlebar.svelte` — defensive `hasTauriRuntime()` guard on all four handlers and the `$effect`. `Titlebar` should never crash if a future code path mounts it without a Tauri runtime; the underlying `getCurrentWindow().metadata` is undefined in plain browsers.
|
||||||
|
|
||||||
|
**Why this lived in v0.1.** The browser-preview path (`npm run dev:frontend`, no Tauri) had never been exercised under headless Chromium with a spoofed UA — Lumotia's dogfood loop runs via `run.sh` which always has the Tauri runtime, so neither bug surfaced.
|
||||||
|
|
||||||
|
## 14. v0.2 release-notes excerpt
|
||||||
|
|
||||||
|
To be filled at Phase 8.
|
||||||
|
|
||||||
|
## 15. Known risks (live)
|
||||||
|
|
||||||
|
- **DictationPage migration before Settings.** Mitigation: only chrome around the record state machine moves to wrappers; state machine + hotkey integration stay verbatim. Per-page gate runs immediately after.
|
||||||
|
- **SettingsPage scale (2 791 LOC, ~95 controls).** Mitigation: section-by-section commits, existing IA preserved, Formsnap selectively.
|
||||||
|
- **Bits UI portals vs Tauri `/preview`.** Mitigation: `/preview` uses zero portaled primitives — only `LumotiaCard`, `LumotiaStatusPill`, `LumotiaButton`.
|
||||||
|
- **Token-name churn breaks mid-migration.** Mitigation: existing token names are stable API; Phase 3 is additive only.
|
||||||
|
- **Sensory-zone CSS combinatorics (3 zones × 2 themes = 6 surface sets).** Mitigation: Playwright tests cycle all 6 in Phase 1.
|
||||||
|
- **Skeleton temptation.** Mitigation: §16 below.
|
||||||
|
- **`@chenglou/pretext` dep in `package.json`** — verify still used; remove if not, during Phase 2.
|
||||||
|
- **KI-05 dual-theme fix.** Mitigation: small, contained edit; resolved in the same commit as Phase 3.
|
||||||
|
- **Identity drift.** Mitigation: every page migration reviewed against the "warm brutalist notebook cockpit" vibe.
|
||||||
|
- **Playwright on Wayland.** `npx playwright install --with-deps chromium` may need extra Linux libs on Fedora. Mitigation: `--with-deps` flag in Phase 1.
|
||||||
|
- **`@vitest/browser` Svelte 5 compat.** Mitigation: `vitest-browser-svelte@2.1.1` is the runes-aware bridge.
|
||||||
|
|
||||||
|
## 16. DO NOT add Skeleton
|
||||||
|
|
||||||
|
Future agents reading "frontend overhaul" may reach for Skeleton. **Do not.** The plan and this doc are explicit: Lumotia's token system and identity are non-negotiable. Bits UI + Formsnap + Superforms is the headless-primitive path. `npm run guard:no-skeleton` fails CI if `@skeletonlabs` appears anywhere in `package.json`, `package-lock.json`, or `src/`.
|
||||||
630
package-lock.json
generated
630
package-lock.json
generated
@@ -20,18 +20,25 @@
|
|||||||
"svelte-i18n": "^4.0.1"
|
"svelte-i18n": "^4.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/adapter-static": "^3.0.10",
|
"@axe-core/playwright": "4.11.3",
|
||||||
"@sveltejs/kit": "^2.58.0",
|
"@playwright/test": "1.60.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
"@sveltejs/adapter-static": "3.0.10",
|
||||||
"@tailwindcss/vite": "^4.2.1",
|
"@sveltejs/kit": "2.58.0",
|
||||||
"@tauri-apps/cli": "^2",
|
"@sveltejs/vite-plugin-svelte": "5.1.1",
|
||||||
|
"@tailwindcss/vite": "4.2.1",
|
||||||
|
"@tauri-apps/cli": "2.10.1",
|
||||||
|
"@vitest/browser": "4.1.6",
|
||||||
|
"@vitest/browser-playwright": "4.1.6",
|
||||||
"jsdom": "29.1.1",
|
"jsdom": "29.1.1",
|
||||||
"svelte": "^5.0.0",
|
"playwright": "1.60.0",
|
||||||
"svelte-check": "^4.0.0",
|
"rollup-plugin-visualizer": "7.0.1",
|
||||||
"tailwindcss": "^4.2.1",
|
"svelte": "5.53.12",
|
||||||
"typescript": "~5.6.2",
|
"svelte-check": "4.4.5",
|
||||||
"vite": "^6.4.2",
|
"tailwindcss": "4.2.1",
|
||||||
"vitest": "4.1.6"
|
"typescript": "5.6.3",
|
||||||
|
"vite": "6.4.2",
|
||||||
|
"vitest": "4.1.6",
|
||||||
|
"vitest-browser-svelte": "2.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@asamuzakjp/css-color": {
|
"node_modules/@asamuzakjp/css-color": {
|
||||||
@@ -85,6 +92,26 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@axe-core/playwright": {
|
||||||
|
"version": "4.11.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.11.3.tgz",
|
||||||
|
"integrity": "sha512-h/kfksv4F0cVIDlKpT4700OehdRgpvuVskuQ2nb7/JmtWUXpe9ftHAPtwyXGvVSsa6SJ64A9ER7Zrzc/sIvC4w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"axe-core": "~4.11.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"playwright-core": ">= 1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@blazediff/core": {
|
||||||
|
"version": "1.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@blazediff/core/-/core-1.9.1.tgz",
|
||||||
|
"integrity": "sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@bramus/specificity": {
|
"node_modules/@bramus/specificity": {
|
||||||
"version": "2.4.2",
|
"version": "2.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz",
|
||||||
@@ -800,6 +827,22 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@playwright/test": {
|
||||||
|
"version": "1.60.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz",
|
||||||
|
"integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"playwright": "1.60.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"playwright": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@polka/url": {
|
"node_modules/@polka/url": {
|
||||||
"version": "1.0.0-next.29",
|
"version": "1.0.0-next.29",
|
||||||
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz",
|
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz",
|
||||||
@@ -1883,6 +1926,19 @@
|
|||||||
"@tauri-apps/api": "^2.8.0"
|
"@tauri-apps/api": "^2.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@testing-library/svelte-core": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@testing-library/svelte-core/-/svelte-core-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-VkUePoLV6oOYwSUvX6ShA8KLnJqZiYMIbP2JW2t0GLWLkJxKGvuH5qrrZBV/X7cXFnLGuFQEC7RheYiZOW68KQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"svelte": "^3 || ^4 || ^5 || ^5.0.0-next.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/chai": {
|
"node_modules/@types/chai": {
|
||||||
"version": "5.2.3",
|
"version": "5.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
|
||||||
@@ -1933,6 +1989,53 @@
|
|||||||
"url": "https://opencollective.com/typescript-eslint"
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@vitest/browser": {
|
||||||
|
"version": "4.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.1.6.tgz",
|
||||||
|
"integrity": "sha512-ynsspTubXGSpa58JFJ24xIQt4z4A25epSbugEyaTmmrV1//Wec9EgE/LtoaC6yxUrXi5P7erGHRrkdZIHaVQuA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@blazediff/core": "1.9.1",
|
||||||
|
"@vitest/mocker": "4.1.6",
|
||||||
|
"@vitest/utils": "4.1.6",
|
||||||
|
"magic-string": "^0.30.21",
|
||||||
|
"pngjs": "^7.0.0",
|
||||||
|
"sirv": "^3.0.2",
|
||||||
|
"tinyrainbow": "^3.1.0",
|
||||||
|
"ws": "^8.19.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vitest": "4.1.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vitest/browser-playwright": {
|
||||||
|
"version": "4.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.1.6.tgz",
|
||||||
|
"integrity": "sha512-4csoeyl/qwHyxU2zNL0++WaoDr8YJDXOQPwWPNJoTZ+QzcdO3INYKgF5Zfz730Io7zbkuv914aZmfQ+QE+1Hvw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vitest/browser": "4.1.6",
|
||||||
|
"@vitest/mocker": "4.1.6",
|
||||||
|
"tinyrainbow": "^3.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"playwright": "*",
|
||||||
|
"vitest": "4.1.6"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"playwright": {
|
||||||
|
"optional": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@vitest/expect": {
|
"node_modules/@vitest/expect": {
|
||||||
"version": "4.1.6",
|
"version": "4.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.6.tgz",
|
||||||
@@ -2068,6 +2171,32 @@
|
|||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ansi-regex": {
|
||||||
|
"version": "6.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
||||||
|
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ansi-styles": {
|
||||||
|
"version": "6.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
|
||||||
|
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/aria-query": {
|
"node_modules/aria-query": {
|
||||||
"version": "5.3.1",
|
"version": "5.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz",
|
||||||
@@ -2087,6 +2216,16 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/axe-core": {
|
||||||
|
"version": "4.11.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.4.tgz",
|
||||||
|
"integrity": "sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/axobject-query": {
|
"node_modules/axobject-query": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
||||||
@@ -2106,6 +2245,22 @@
|
|||||||
"require-from-string": "^2.0.2"
|
"require-from-string": "^2.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/bundle-name": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"run-applescript": "^7.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chai": {
|
"node_modules/chai": {
|
||||||
"version": "6.2.2",
|
"version": "6.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
|
||||||
@@ -2148,6 +2303,21 @@
|
|||||||
"node": ">=0.10"
|
"node": ">=0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cliui": {
|
||||||
|
"version": "9.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz",
|
||||||
|
"integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"string-width": "^7.2.0",
|
||||||
|
"strip-ansi": "^7.1.0",
|
||||||
|
"wrap-ansi": "^9.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/clsx": {
|
"node_modules/clsx": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||||
@@ -2248,6 +2418,49 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/default-browser": {
|
||||||
|
"version": "5.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz",
|
||||||
|
"integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"bundle-name": "^4.1.0",
|
||||||
|
"default-browser-id": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/default-browser-id": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/define-lazy-prop": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/detect-libc": {
|
"node_modules/detect-libc": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||||
@@ -2264,6 +2477,13 @@
|
|||||||
"integrity": "sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==",
|
"integrity": "sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/emoji-regex": {
|
||||||
|
"version": "10.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
|
||||||
|
"integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/enhanced-resolve": {
|
"node_modules/enhanced-resolve": {
|
||||||
"version": "5.20.0",
|
"version": "5.20.0",
|
||||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz",
|
||||||
@@ -2392,6 +2612,16 @@
|
|||||||
"@esbuild/win32-x64": "0.25.12"
|
"@esbuild/win32-x64": "0.25.12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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/esm-env": {
|
"node_modules/esm-env": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
|
||||||
@@ -2491,6 +2721,29 @@
|
|||||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/get-caller-file": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": "6.* || 8.* || >= 10.*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/get-east-asian-width": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/globalyzer": {
|
"node_modules/globalyzer": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz",
|
||||||
@@ -2535,6 +2788,54 @@
|
|||||||
"tslib": "^2.8.0"
|
"tslib": "^2.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-docker": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"is-docker": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-in-ssh": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-inside-container": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-docker": "^3.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"is-inside-container": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-potential-custom-element-name": {
|
"node_modules/is-potential-custom-element-name": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
|
||||||
@@ -2557,6 +2858,22 @@
|
|||||||
"@types/estree": "^1.0.6"
|
"@types/estree": "^1.0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-wsl": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-inside-container": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/jiti": {
|
"node_modules/jiti": {
|
||||||
"version": "2.6.1",
|
"version": "2.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
|
||||||
@@ -3010,6 +3327,27 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/open": {
|
||||||
|
"version": "11.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz",
|
||||||
|
"integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"default-browser": "^5.4.0",
|
||||||
|
"define-lazy-prop": "^3.0.0",
|
||||||
|
"is-in-ssh": "^1.0.0",
|
||||||
|
"is-inside-container": "^1.0.0",
|
||||||
|
"powershell-utils": "^0.1.0",
|
||||||
|
"wsl-utils": "^0.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/parse5": {
|
"node_modules/parse5": {
|
||||||
"version": "8.0.1",
|
"version": "8.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz",
|
||||||
@@ -3050,6 +3388,63 @@
|
|||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/playwright": {
|
||||||
|
"version": "1.60.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz",
|
||||||
|
"integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"playwright-core": "1.60.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"playwright": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "2.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/playwright-core": {
|
||||||
|
"version": "1.60.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz",
|
||||||
|
"integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"playwright-core": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/playwright/node_modules/fsevents": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pngjs": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.19.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.8",
|
"version": "8.5.8",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
|
||||||
@@ -3079,6 +3474,19 @@
|
|||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/powershell-utils": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz",
|
||||||
|
"integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/punycode": {
|
"node_modules/punycode": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||||
@@ -3158,6 +3566,50 @@
|
|||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rollup-plugin-visualizer": {
|
||||||
|
"version": "7.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-7.0.1.tgz",
|
||||||
|
"integrity": "sha512-UJUT4+1Ho4OcWmPYU3sYXgUqI8B8Ayfe06MX7y0qCJ1K8aGoKtR/NDd/2nZqM7ADkrzny+I99Ul7GgyoiVNAgg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"open": "^11.0.0",
|
||||||
|
"picomatch": "^4.0.2",
|
||||||
|
"source-map": "^0.7.4",
|
||||||
|
"yargs": "^18.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"rollup-plugin-visualizer": "dist/bin/cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"rolldown": "1.x || ^1.0.0-beta || ^1.0.0-rc",
|
||||||
|
"rollup": "2.x || 3.x || 4.x"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"rolldown": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"rollup": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/run-applescript": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/sade": {
|
"node_modules/sade": {
|
||||||
"version": "1.8.1",
|
"version": "1.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
|
||||||
@@ -3212,6 +3664,16 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/source-map": {
|
||||||
|
"version": "0.7.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
|
||||||
|
"integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
@@ -3236,6 +3698,40 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/string-width": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"emoji-regex": "^10.3.0",
|
||||||
|
"get-east-asian-width": "^1.0.0",
|
||||||
|
"strip-ansi": "^7.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/strip-ansi": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": "^6.2.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/svelte": {
|
"node_modules/svelte": {
|
||||||
"version": "5.53.12",
|
"version": "5.53.12",
|
||||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.53.12.tgz",
|
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.53.12.tgz",
|
||||||
@@ -4089,6 +4585,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vitest-browser-svelte": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/vitest-browser-svelte/-/vitest-browser-svelte-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-qbunYRSm+N92r9bfTkdDTpBZESLmp4QFz2SluV3n/x8U7ysosfeXYJZ4vXbJ0Y0LzoqqDnV5LHprmFgn4Eo+Ug==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@testing-library/svelte-core": "^1.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"svelte": "^3 || ^4 || ^5 || ^5.0.0-next.0",
|
||||||
|
"vitest": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/w3c-xmlserializer": {
|
"node_modules/w3c-xmlserializer": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
|
||||||
@@ -4154,6 +4667,63 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/wrap-ansi": {
|
||||||
|
"version": "9.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
|
||||||
|
"integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": "^6.2.1",
|
||||||
|
"string-width": "^7.0.0",
|
||||||
|
"strip-ansi": "^7.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ws": {
|
||||||
|
"version": "8.20.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz",
|
||||||
|
"integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": ">=5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/wsl-utils": {
|
||||||
|
"version": "0.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz",
|
||||||
|
"integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-wsl": "^3.1.0",
|
||||||
|
"powershell-utils": "^0.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/xml-name-validator": {
|
"node_modules/xml-name-validator": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
|
||||||
@@ -4171,6 +4741,44 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/y18n": {
|
||||||
|
"version": "5.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||||
|
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yargs": {
|
||||||
|
"version": "18.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz",
|
||||||
|
"integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cliui": "^9.0.1",
|
||||||
|
"escalade": "^3.1.1",
|
||||||
|
"get-caller-file": "^2.0.5",
|
||||||
|
"string-width": "^7.2.0",
|
||||||
|
"y18n": "^5.0.5",
|
||||||
|
"yargs-parser": "^22.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || ^22.12.0 || >=23"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yargs-parser": {
|
||||||
|
"version": "22.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz",
|
||||||
|
"integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || ^22.12.0 || >=23"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/zimmerframe": {
|
"node_modules/zimmerframe": {
|
||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz",
|
||||||
|
|||||||
15
package.json
15
package.json
@@ -13,6 +13,12 @@
|
|||||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"test:watch": "vitest",
|
"test:watch": "vitest",
|
||||||
|
"test:e2e": "playwright test",
|
||||||
|
"test:e2e:ui": "playwright test --ui",
|
||||||
|
"test:browser": "vitest --config vitest.browser.config.js --run",
|
||||||
|
"test:rust:fast": "cargo nextest run --workspace",
|
||||||
|
"analyze": "ANALYZE=1 vite build",
|
||||||
|
"guard:no-skeleton": "node scripts/guard-no-skeleton.mjs",
|
||||||
"tauri": "tauri"
|
"tauri": "tauri"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -28,17 +34,24 @@
|
|||||||
"svelte-i18n": "^4.0.1"
|
"svelte-i18n": "^4.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@axe-core/playwright": "4.11.3",
|
||||||
|
"@playwright/test": "1.60.0",
|
||||||
"@sveltejs/adapter-static": "3.0.10",
|
"@sveltejs/adapter-static": "3.0.10",
|
||||||
"@sveltejs/kit": "2.58.0",
|
"@sveltejs/kit": "2.58.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "5.1.1",
|
"@sveltejs/vite-plugin-svelte": "5.1.1",
|
||||||
"@tailwindcss/vite": "4.2.1",
|
"@tailwindcss/vite": "4.2.1",
|
||||||
"@tauri-apps/cli": "2.10.1",
|
"@tauri-apps/cli": "2.10.1",
|
||||||
|
"@vitest/browser": "4.1.6",
|
||||||
|
"@vitest/browser-playwright": "4.1.6",
|
||||||
"jsdom": "29.1.1",
|
"jsdom": "29.1.1",
|
||||||
|
"playwright": "1.60.0",
|
||||||
|
"rollup-plugin-visualizer": "7.0.1",
|
||||||
"svelte": "5.53.12",
|
"svelte": "5.53.12",
|
||||||
"svelte-check": "4.4.5",
|
"svelte-check": "4.4.5",
|
||||||
"tailwindcss": "4.2.1",
|
"tailwindcss": "4.2.1",
|
||||||
"typescript": "5.6.3",
|
"typescript": "5.6.3",
|
||||||
"vite": "6.4.2",
|
"vite": "6.4.2",
|
||||||
"vitest": "4.1.6"
|
"vitest": "4.1.6",
|
||||||
|
"vitest-browser-svelte": "2.1.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
39
playwright.config.ts
Normal file
39
playwright.config.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { defineConfig, devices } from "@playwright/test";
|
||||||
|
|
||||||
|
// Frontend-only E2E. The dev server is SvelteKit (npm run dev:frontend) on
|
||||||
|
// localhost:1420 — Tauri IPC is not present. Tests must not depend on Tauri
|
||||||
|
// commands; mock the invoke boundary if you need a Tauri-flavoured surface,
|
||||||
|
// or skip the assertion with a reason and cover it via `cargo test` instead.
|
||||||
|
//
|
||||||
|
// Visual diffs are NOT failing the build yet; screenshots are captured as
|
||||||
|
// artifacts. After Phase 7 stabilises the UI, a follow-up commit promotes
|
||||||
|
// baselines and enables failing diffs.
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: "tests/e2e",
|
||||||
|
fullyParallel: true,
|
||||||
|
forbidOnly: !!process.env.CI,
|
||||||
|
retries: process.env.CI ? 2 : 0,
|
||||||
|
reporter: [["list"], ["html", { open: "never", outputFolder: "playwright-report" }]],
|
||||||
|
outputDir: "test-results",
|
||||||
|
use: {
|
||||||
|
baseURL: "http://localhost:1420",
|
||||||
|
trace: "on-first-retry",
|
||||||
|
screenshot: "only-on-failure",
|
||||||
|
},
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: "chromium-900x700",
|
||||||
|
use: { ...devices["Desktop Chrome"], viewport: { width: 900, height: 700 } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "chromium-1440x900",
|
||||||
|
use: { ...devices["Desktop Chrome"], viewport: { width: 1440, height: 900 } },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
webServer: {
|
||||||
|
command: "npm run dev:frontend",
|
||||||
|
url: "http://localhost:1420",
|
||||||
|
reuseExistingServer: !process.env.CI,
|
||||||
|
timeout: 120_000,
|
||||||
|
},
|
||||||
|
});
|
||||||
67
scripts/guard-no-skeleton.mjs
Normal file
67
scripts/guard-no-skeleton.mjs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
// Fails (exit 1) if any reference to @skeletonlabs appears in package.json,
|
||||||
|
// package-lock.json, or anywhere under src/. Prevents a future agent
|
||||||
|
// reading "frontend overhaul" from reaching for Skeleton — see
|
||||||
|
// docs/release/v0.2-frontend-overhaul.md §16.
|
||||||
|
|
||||||
|
import { readFileSync, readdirSync, statSync } from "node:fs";
|
||||||
|
import { join, relative } from "node:path";
|
||||||
|
|
||||||
|
const ROOT = process.cwd();
|
||||||
|
const NEEDLE = "@skeletonlabs";
|
||||||
|
const hits = [];
|
||||||
|
|
||||||
|
function checkFile(path) {
|
||||||
|
try {
|
||||||
|
const content = readFileSync(path, "utf8");
|
||||||
|
if (content.includes(NEEDLE)) {
|
||||||
|
const rel = relative(ROOT, path);
|
||||||
|
const lines = content.split("\n");
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
if (lines[i].includes(NEEDLE)) {
|
||||||
|
hits.push(`${rel}:${i + 1}: ${lines[i].trim()}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// unreadable -> ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function walk(dir) {
|
||||||
|
let entries;
|
||||||
|
try {
|
||||||
|
entries = readdirSync(dir);
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const name of entries) {
|
||||||
|
if (name === "node_modules" || name === ".svelte-kit" || name === ".git") continue;
|
||||||
|
const full = join(dir, name);
|
||||||
|
let st;
|
||||||
|
try {
|
||||||
|
st = statSync(full);
|
||||||
|
} catch {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (st.isDirectory()) {
|
||||||
|
walk(full);
|
||||||
|
} else {
|
||||||
|
checkFile(full);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkFile(join(ROOT, "package.json"));
|
||||||
|
checkFile(join(ROOT, "package-lock.json"));
|
||||||
|
walk(join(ROOT, "src"));
|
||||||
|
|
||||||
|
if (hits.length > 0) {
|
||||||
|
console.error(`guard-no-skeleton: found ${hits.length} reference(s) to ${NEEDLE}:`);
|
||||||
|
for (const h of hits) console.error(" " + h);
|
||||||
|
console.error("\nLumotia v0.2 frontend overhaul forbids Skeleton.");
|
||||||
|
console.error("See docs/release/v0.2-frontend-overhaul.md §16.");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`guard-no-skeleton: clean (no ${NEEDLE} references in package.json, package-lock.json, or src/)`);
|
||||||
@@ -1,33 +1,48 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||||
import { settings } from "$lib/stores/page.svelte.js";
|
import { settings } from "$lib/stores/page.svelte.js";
|
||||||
|
import { hasTauriRuntime } from "$lib/utils/runtime.js";
|
||||||
|
|
||||||
let { compact = false } = $props();
|
let { compact = false } = $props();
|
||||||
|
|
||||||
let maximised = $state(false);
|
let maximised = $state(false);
|
||||||
|
|
||||||
|
// Titlebar is normally gated behind useCustomChrome in the route layouts,
|
||||||
|
// which is false on Linux (native KWin/Mutter decorations) and false in
|
||||||
|
// any browser-preview context that mis-detects the OS. The defensive
|
||||||
|
// guard here keeps the component from crashing in any future code path
|
||||||
|
// that mounts it without a Tauri runtime — `getCurrentWindow()` reads
|
||||||
|
// window.__TAURI_INTERNALS__.metadata which is undefined in plain browsers.
|
||||||
|
const tauriPresent = hasTauriRuntime();
|
||||||
|
|
||||||
async function handleMinimise() {
|
async function handleMinimise() {
|
||||||
|
if (!tauriPresent) return;
|
||||||
await getCurrentWindow().minimize();
|
await getCurrentWindow().minimize();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleMaximise() {
|
async function handleMaximise() {
|
||||||
|
if (!tauriPresent) return;
|
||||||
await getCurrentWindow().toggleMaximize();
|
await getCurrentWindow().toggleMaximize();
|
||||||
maximised = await getCurrentWindow().isMaximized();
|
maximised = await getCurrentWindow().isMaximized();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleClose() {
|
async function handleClose() {
|
||||||
|
if (!tauriPresent) return;
|
||||||
await getCurrentWindow().hide();
|
await getCurrentWindow().hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDragStart(e: PointerEvent) {
|
function handleDragStart(e: PointerEvent) {
|
||||||
|
if (!tauriPresent) return;
|
||||||
if (e.button !== 0) return;
|
if (e.button !== 0) return;
|
||||||
if ((e.target as HTMLElement | null)?.closest("button")) return;
|
if ((e.target as HTMLElement | null)?.closest("button")) return;
|
||||||
try { (e.currentTarget as HTMLElement | null)?.setPointerCapture?.(e.pointerId); } catch {}
|
try { (e.currentTarget as HTMLElement | null)?.setPointerCapture?.(e.pointerId); } catch {}
|
||||||
getCurrentWindow().startDragging();
|
getCurrentWindow().startDragging();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track maximise state
|
// Track maximise state. Skipped without Tauri; getCurrentWindow().onResized
|
||||||
|
// throws because the underlying Window object has no `metadata`.
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
|
if (!tauriPresent) return;
|
||||||
let unlisten: (() => void) | undefined;
|
let unlisten: (() => void) | undefined;
|
||||||
getCurrentWindow().onResized(async () => {
|
getCurrentWindow().onResized(async () => {
|
||||||
maximised = await getCurrentWindow().isMaximized();
|
maximised = await getCurrentWindow().isMaximized();
|
||||||
|
|||||||
@@ -30,23 +30,37 @@ interface OsInfo {
|
|||||||
|
|
||||||
let cached: OsInfo | null = null;
|
let cached: OsInfo | null = null;
|
||||||
|
|
||||||
const FALLBACK_BROWSER_INFO = {
|
// Lazy fallback builder. The previous module-level constant evaluated
|
||||||
os: detectBrowserOs(),
|
// `navigator` during SvelteKit SSR — where it is undefined — so `os`
|
||||||
arch: 'unknown',
|
// got frozen at `'unknown'` and `isLinux()` returned false even when
|
||||||
family: detectBrowserFamily(),
|
// the page rendered in a real Linux browser. Building on demand reads
|
||||||
usesCmd: detectBrowserOs() === 'macos',
|
// `navigator` at call-time, which is always client-side.
|
||||||
isWayland: false,
|
function buildBrowserFallback(): OsInfo {
|
||||||
customHotkeyBackend: false,
|
const browserOs = detectBrowserOs();
|
||||||
primaryModifierLabel: detectBrowserOs() === 'macos' ? 'Cmd' : 'Ctrl',
|
return {
|
||||||
};
|
os: browserOs,
|
||||||
|
arch: 'unknown',
|
||||||
|
family: detectBrowserFamily(),
|
||||||
|
usesCmd: browserOs === 'macos',
|
||||||
|
isWayland: false,
|
||||||
|
customHotkeyBackend: false,
|
||||||
|
primaryModifierLabel: browserOs === 'macos' ? 'Cmd' : 'Ctrl',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function detectBrowserOs() {
|
function detectBrowserOs() {
|
||||||
if (typeof navigator === 'undefined') return 'unknown';
|
if (typeof navigator === 'undefined') return 'unknown';
|
||||||
const ua = (navigator.userAgent || '').toLowerCase();
|
// navigator.platform is the truthful OS surface and survives UA spoofing
|
||||||
|
// (e.g. Playwright's Chromium reports a Windows UA on Linux runners). UA
|
||||||
|
// is consulted only as a fallback when platform is empty or unrecognised.
|
||||||
const platform = (navigator.platform || '').toLowerCase();
|
const platform = (navigator.platform || '').toLowerCase();
|
||||||
if (platform.includes('mac') || ua.includes('mac os')) return 'macos';
|
if (platform.includes('mac')) return 'macos';
|
||||||
if (platform.includes('win') || ua.includes('windows')) return 'windows';
|
if (platform.includes('win')) return 'windows';
|
||||||
if (platform.includes('linux') || ua.includes('linux')) return 'linux';
|
if (platform.includes('linux')) return 'linux';
|
||||||
|
const ua = (navigator.userAgent || '').toLowerCase();
|
||||||
|
if (ua.includes('mac os')) return 'macos';
|
||||||
|
if (ua.includes('windows')) return 'windows';
|
||||||
|
if (ua.includes('linux')) return 'linux';
|
||||||
return 'unknown';
|
return 'unknown';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,14 +79,14 @@ function detectBrowserFamily() {
|
|||||||
export async function loadOsInfo(): Promise<OsInfo> {
|
export async function loadOsInfo(): Promise<OsInfo> {
|
||||||
if (cached) return cached;
|
if (cached) return cached;
|
||||||
if (!hasTauriRuntime()) {
|
if (!hasTauriRuntime()) {
|
||||||
cached = FALLBACK_BROWSER_INFO;
|
cached = buildBrowserFallback();
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
cached = await invoke<OsInfo>("get_os_info");
|
cached = await invoke<OsInfo>("get_os_info");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('loadOsInfo: get_os_info failed, using browser fallback', err);
|
console.warn('loadOsInfo: get_os_info failed, using browser fallback', err);
|
||||||
cached = FALLBACK_BROWSER_INFO;
|
cached = buildBrowserFallback();
|
||||||
}
|
}
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
|
|||||||
91
tests/e2e/smoke.spec.ts
Normal file
91
tests/e2e/smoke.spec.ts
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
// Phase 1 smoke tests. Frontend-only — no Tauri IPC.
|
||||||
|
// Visual screenshots are saved as artifacts; they do NOT fail the build at
|
||||||
|
// this stage (see playwright.config.ts header). Promotion to failing visual
|
||||||
|
// diffs happens after Phase 7 stabilises the UI.
|
||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import AxeBuilder from "@axe-core/playwright";
|
||||||
|
|
||||||
|
const ZONES = ["cave", "energy", "reset"] as const;
|
||||||
|
const THEMES = ["light", "dark"] as const;
|
||||||
|
|
||||||
|
test.describe("Lumotia frontend smoke (no Tauri IPC)", () => {
|
||||||
|
test("app loads without Tauri runtime", async ({ page }) => {
|
||||||
|
// Suppress noisy invoke-rejected promises from the layout's onMount;
|
||||||
|
// they are expected without Tauri. We only fail on unhandled crashes.
|
||||||
|
const fatalErrors: string[] = [];
|
||||||
|
page.on("pageerror", (err) => fatalErrors.push(err.message));
|
||||||
|
|
||||||
|
await page.goto("/");
|
||||||
|
await expect(page.locator("main")).toBeVisible();
|
||||||
|
expect(fatalErrors).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("keyboard navigation reaches main + sidebar surface", async ({ page }) => {
|
||||||
|
await page.goto("/");
|
||||||
|
await page.locator("main").waitFor({ state: "visible" });
|
||||||
|
// Tab once and confirm focus moves to a focusable element somewhere in
|
||||||
|
// the document, not nothing. We don't assert the specific element to
|
||||||
|
// keep the test robust through chrome migration.
|
||||||
|
await page.keyboard.press("Tab");
|
||||||
|
const focused = await page.evaluate(() =>
|
||||||
|
document.activeElement && document.activeElement !== document.body
|
||||||
|
? document.activeElement.tagName
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
expect(focused).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("axe scan on / has no critical structural violations", async ({ page }) => {
|
||||||
|
await page.goto("/");
|
||||||
|
await page.locator("main").waitFor({ state: "visible" });
|
||||||
|
const results = await new AxeBuilder({ page })
|
||||||
|
.disableRules([
|
||||||
|
// svelte-i18n initialises asynchronously; <html lang> is set after
|
||||||
|
// first paint in browser-preview mode.
|
||||||
|
"html-has-lang",
|
||||||
|
// Colour-contrast is owned by docs/release/v0.1-contrast-audit.md
|
||||||
|
// and gets re-enabled in Phase 7 after the wrapper palette settles.
|
||||||
|
// Phase 1's smoke baseline focuses on structural/semantic a11y.
|
||||||
|
"color-contrast",
|
||||||
|
])
|
||||||
|
.analyze();
|
||||||
|
const criticals = results.violations.filter(
|
||||||
|
(v) => v.impact === "critical" || v.impact === "serious",
|
||||||
|
);
|
||||||
|
expect(criticals).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const theme of THEMES) {
|
||||||
|
test(`theme smoke — ${theme}`, async ({ page }, testInfo) => {
|
||||||
|
await page.goto("/");
|
||||||
|
await page.locator("main").waitFor({ state: "visible" });
|
||||||
|
// Apply theme without depending on the preferences store, which
|
||||||
|
// needs Tauri persistence. The data-theme attribute is what the CSS
|
||||||
|
// tokens key off; the preferences store also writes this attribute
|
||||||
|
// in production.
|
||||||
|
await page.evaluate((t) => {
|
||||||
|
document.documentElement.dataset.theme = t;
|
||||||
|
}, theme);
|
||||||
|
await page.waitForTimeout(50);
|
||||||
|
await page.screenshot({
|
||||||
|
path: `test-results/theme-${theme}-${testInfo.project.name}.png`,
|
||||||
|
fullPage: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const zone of ZONES) {
|
||||||
|
test(`sensory zone smoke — ${zone}`, async ({ page }, testInfo) => {
|
||||||
|
await page.goto("/");
|
||||||
|
await page.locator("main").waitFor({ state: "visible" });
|
||||||
|
await page.evaluate((z) => {
|
||||||
|
document.documentElement.dataset.zone = z;
|
||||||
|
}, zone);
|
||||||
|
await page.waitForTimeout(50);
|
||||||
|
await page.screenshot({
|
||||||
|
path: `test-results/zone-${zone}-${testInfo.project.name}.png`,
|
||||||
|
fullPage: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -4,13 +4,30 @@
|
|||||||
import { defineConfig } from "vitest/config";
|
import { defineConfig } from "vitest/config";
|
||||||
import { sveltekit } from "@sveltejs/kit/vite";
|
import { sveltekit } from "@sveltejs/kit/vite";
|
||||||
import tailwindcss from "@tailwindcss/vite";
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
|
import { visualizer } from "rollup-plugin-visualizer";
|
||||||
|
|
||||||
// @ts-expect-error process is a nodejs global
|
// @ts-expect-error process is a nodejs global
|
||||||
const host = process.env.TAURI_DEV_HOST;
|
const host = process.env.TAURI_DEV_HOST;
|
||||||
|
// @ts-expect-error process is a nodejs global
|
||||||
|
const analyze = process.env.ANALYZE === "1";
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig(async () => ({
|
export default defineConfig(async () => ({
|
||||||
plugins: [sveltekit(), tailwindcss()],
|
plugins: [
|
||||||
|
sveltekit(),
|
||||||
|
tailwindcss(),
|
||||||
|
...(analyze
|
||||||
|
? [
|
||||||
|
visualizer({
|
||||||
|
filename: "reports/bundle-stats.html",
|
||||||
|
template: "treemap",
|
||||||
|
gzipSize: true,
|
||||||
|
brotliSize: true,
|
||||||
|
open: false,
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
],
|
||||||
clearScreen: false,
|
clearScreen: false,
|
||||||
server: {
|
server: {
|
||||||
port: 1420,
|
port: 1420,
|
||||||
|
|||||||
29
vitest.browser.config.js
Normal file
29
vitest.browser.config.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// Browser-mode vitest config. Separate from the jsdom suite in vite.config.js
|
||||||
|
// so we never accidentally cross-pollute environments. Includes only
|
||||||
|
// component-level tests under src/lib/ui/** and src/lib/shell/**, the
|
||||||
|
// new homes for v0.2 primitives and the split shell.
|
||||||
|
import { defineConfig } from "vitest/config";
|
||||||
|
import { sveltekit } from "@sveltejs/kit/vite";
|
||||||
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
|
import { playwright } from "@vitest/browser-playwright";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [sveltekit(), tailwindcss()],
|
||||||
|
test: {
|
||||||
|
include: [
|
||||||
|
"src/lib/ui/**/*.browser.{test,spec}.{ts,js,svelte}",
|
||||||
|
"src/lib/shell/**/*.browser.{test,spec}.{ts,js,svelte}",
|
||||||
|
"src/lib/utils/**/*.browser.{test,spec}.{ts,js}",
|
||||||
|
],
|
||||||
|
exclude: ["src-tauri/**", "node_modules/**", "build/**", ".svelte-kit/**"],
|
||||||
|
browser: {
|
||||||
|
enabled: true,
|
||||||
|
provider: playwright(),
|
||||||
|
headless: true,
|
||||||
|
instances: [{ browser: "chromium" }],
|
||||||
|
},
|
||||||
|
restoreMocks: true,
|
||||||
|
clearMocks: true,
|
||||||
|
unstubAllGlobals: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user