feat: add React frontend with search and mod pages

Add apps/web (Vite + React Router + Tailwind v4 + Zustand) with a search page and a mod page that analyzes saves via @tts/extract. Document the frontend in the README and docs.
This commit is contained in:
2026-08-08 11:22:09 +08:00
parent b32cb53c67
commit 7c812a109c
21 changed files with 1067 additions and 30 deletions
+6
View File
@@ -7,6 +7,7 @@ analyze their contents. A lightweight, client-only pnpm monorepo.
| Package | Role | Runtime | | Package | Role | Runtime |
| ------------------- | ------------------------------------------------------ | ---------- | | ------------------- | ------------------------------------------------------ | ---------- |
| `apps/web` | React frontend: search + mod pages | Browser |
| `apps/proxy` | Hono HTTP server: Workshop search + save fetch | Node | | `apps/proxy` | Hono HTTP server: Workshop search + save fetch | Node |
| `packages/tts` | Fetch save from Steam, BSON-parse to `TTSMod` | Node | | `packages/tts` | Fetch save from Steam, BSON-parse to `TTSMod` | Node |
| `packages/extract` | Analyze a `TTSMod`: objects, asset refs, downloads | Isomorphic | | `packages/extract` | Analyze a `TTSMod`: objects, asset refs, downloads | Isomorphic |
@@ -25,6 +26,10 @@ pnpm dev # runs the proxy at http://localhost:3000
Get a Steam Web API key at https://steamcommunity.com/dev/apikey (free). Get a Steam Web API key at https://steamcommunity.com/dev/apikey (free).
To run the frontend alongside the proxy, open a second terminal and run
`pnpm --filter @tts/web dev` (serves at http://localhost:5173 and proxies
`/search`, `/items`, and `/health` to the backend).
## API ## API
| Method | Path | Description | | Method | Path | Description |
@@ -38,6 +43,7 @@ Get a Steam Web API key at https://steamcommunity.com/dev/apikey (free).
```sh ```sh
pnpm dev # run the proxy (tsx watch) pnpm dev # run the proxy (tsx watch)
pnpm dev:web # run the frontend (vite)
pnpm build # compile all packages pnpm build # compile all packages
pnpm typecheck # typecheck all packages pnpm typecheck # typecheck all packages
pnpm test # run the unit tests (vitest) pnpm test # run the unit tests (vitest)
+12
View File
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>TTS Workshop</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+32
View File
@@ -0,0 +1,32 @@
{
"name": "@tts/web",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -p tsconfig.json && vite build",
"preview": "vite preview",
"typecheck": "tsc -p tsconfig.json --noEmit",
"test": "vitest run",
"lint": "echo \"no lint configured\""
},
"dependencies": {
"@tts/extract": "workspace:*",
"@tts/shared": "workspace:*",
"react": "^19.2.8",
"react-dom": "^19.2.8",
"react-router-dom": "^7.18.2",
"zustand": "^5.0.14"
},
"devDependencies": {
"@tailwindcss/vite": "^4.3.3",
"@types/react": "^19.2.18",
"@types/react-dom": "^19.2.4",
"@vitejs/plugin-react": "^6.0.5",
"tailwindcss": "^4.3.3",
"typescript": "^5.7.2",
"vite": "^8.2.1",
"vitest": "^4.1.10"
}
}
+28
View File
@@ -0,0 +1,28 @@
import { Link, Route, Routes } from 'react-router-dom';
import SearchPage from './pages/SearchPage';
import ModPage from './pages/ModPage';
export default function App() {
return (
<div className="min-h-screen bg-zinc-950 text-zinc-100">
<header className="border-b border-zinc-800">
<div className="mx-auto flex max-w-5xl items-center justify-between px-4 py-4">
<Link to="/" className="text-lg font-semibold tracking-tight">
TTS Workshop
</Link>
<nav className="flex gap-4 text-sm text-zinc-400">
<Link to="/" className="hover:text-zinc-100">
Search
</Link>
</nav>
</div>
</header>
<main className="mx-auto max-w-5xl px-4 py-8">
<Routes>
<Route path="/" element={<SearchPage />} />
<Route path="/mod/:id" element={<ModPage />} />
</Routes>
</main>
</div>
);
}
+28
View File
@@ -0,0 +1,28 @@
import type { SearchResult, TTSMod } from '@tts/shared';
const BASE = '';
async function getJson<T>(path: string): Promise<T> {
const res = await fetch(`${BASE}${path}`);
if (!res.ok) {
const body = (await res.json().catch(() => null)) as { error?: string } | null;
throw new Error(body?.error ?? `Request failed (${res.status})`);
}
return res.json() as Promise<T>;
}
/** Search the Workshop. */
export function searchWorkshop(q: string, page = 1): Promise<SearchResult> {
const params = new URLSearchParams({ q, page: String(page) });
return getJson<SearchResult>(`/search?${params.toString()}`);
}
/** Fetch a full parsed TTS save. */
export function fetchMod(id: string): Promise<TTSMod> {
return getJson<TTSMod>(`/items/${id}`);
}
/** Build a URL for the raw save file download. */
export function modFileUrl(id: string): string {
return `${BASE}/items/${id}/file`;
}
+57
View File
@@ -0,0 +1,57 @@
import { Link } from 'react-router-dom';
import { useSearchStore } from '../stores/searchStore';
export default function SearchResults() {
const { items, page, hasMore, loading, search } = useSearchStore();
if (loading) return null;
if (items.length === 0) {
return <p className="text-sm text-zinc-500">No results yet.</p>;
}
return (
<div className="space-y-4">
<ul className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
{items.map((item) => (
<li key={item.id}>
<Link
to={`/mod/${item.id}`}
className="block overflow-hidden rounded-lg border border-zinc-800 bg-zinc-900 hover:border-zinc-600"
>
{item.previewImageUrl && (
<img
src={item.previewImageUrl}
alt={item.title}
className="h-40 w-full object-cover"
loading="lazy"
/>
)}
<div className="p-3">
<h3 className="truncate font-medium">{item.title}</h3>
<p className="truncate text-sm text-zinc-400">{item.author}</p>
</div>
</Link>
</li>
))}
</ul>
<div className="flex items-center justify-between text-sm">
<button
onClick={() => search(useSearchStore.getState().query, page - 1)}
disabled={page <= 1 || loading}
className="rounded-lg border border-zinc-700 px-3 py-1.5 hover:bg-zinc-800 disabled:opacity-40"
>
Previous
</button>
<span className="text-zinc-400">Page {page}</span>
<button
onClick={() => search(useSearchStore.getState().query, page + 1)}
disabled={!hasMore || loading}
className="rounded-lg border border-zinc-700 px-3 py-1.5 hover:bg-zinc-800 disabled:opacity-40"
>
Next
</button>
</div>
</div>
);
}
+1
View File
@@ -0,0 +1 @@
@import "tailwindcss";
+13
View File
@@ -0,0 +1,13 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import './index.css';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</StrictMode>,
);
+76
View File
@@ -0,0 +1,76 @@
import { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { collectRefs, flattenObjects } from '@tts/extract';
import { useModStore } from '../stores/modStore';
import { modFileUrl } from '../api';
export default function ModPage() {
const { id } = useParams<{ id: string }>();
const { mod, loading, error, load } = useModStore();
useEffect(() => {
if (id) load(id);
}, [id, load]);
if (loading) return <p className="text-sm text-zinc-400">Loading mod</p>;
if (error) return <p className="text-sm text-red-400">{error}</p>;
if (!mod) return <p className="text-sm text-zinc-500">No mod loaded.</p>;
const objects = flattenObjects(mod);
const refs = collectRefs(mod);
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-semibold">Mod {id}</h1>
<p className="mt-1 text-sm text-zinc-400">
{mod.GameMode} · {mod.Date} · {objects.length} objects · {refs.length}{' '}
asset refs
</p>
<a
href={modFileUrl(id!)}
className="mt-3 inline-block rounded-lg bg-zinc-100 px-4 py-2 text-sm font-medium text-zinc-900 hover:bg-zinc-300"
>
Download save file
</a>
</div>
<section>
<h2 className="mb-2 text-lg font-semibold">Asset references</h2>
{refs.length === 0 ? (
<p className="text-sm text-zinc-500">No external assets found.</p>
) : (
<ul className="space-y-1">
{refs.map((ref) => (
<li key={ref.url} className="flex items-center gap-2 text-sm">
<span className="rounded bg-zinc-800 px-1.5 py-0.5 text-xs uppercase text-zinc-400">
{ref.kind}
</span>
<a
href={ref.url}
target="_blank"
rel="noreferrer"
className="truncate text-zinc-300 hover:text-zinc-100"
>
{ref.url}
</a>
</li>
))}
</ul>
)}
</section>
<section>
<h2 className="mb-2 text-lg font-semibold">Objects</h2>
<ul className="divide-y divide-zinc-800 rounded-lg border border-zinc-800">
{objects.map((o) => (
<li key={o.GUID} className="flex items-center justify-between px-3 py-2 text-sm">
<span className="font-medium">{o.Name}</span>
<span className="font-mono text-xs text-zinc-500">{o.GUID}</span>
</li>
))}
</ul>
</section>
</div>
);
}
+48
View File
@@ -0,0 +1,48 @@
import { useEffect, useState } from 'react';
import { useSearchStore } from '../stores/searchStore';
import SearchResults from '../components/SearchResults';
export default function SearchPage() {
const { query, search, loading, error } = useSearchStore();
const [input, setInput] = useState(query);
useEffect(() => {
if (query) search(query);
}, [query, search]);
const onSubmit = (e: React.FormEvent) => {
e.preventDefault();
search(input);
};
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-semibold">Search the Workshop</h1>
<p className="mt-1 text-sm text-zinc-400">
Find Tabletop Simulator Workshop items by name.
</p>
</div>
<form onSubmit={onSubmit} className="flex gap-2">
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="e.g. wingspan"
className="flex-1 rounded-lg border border-zinc-700 bg-zinc-900 px-4 py-2 text-sm outline-none focus:border-zinc-500"
/>
<button
type="submit"
disabled={loading}
className="rounded-lg bg-zinc-100 px-4 py-2 text-sm font-medium text-zinc-900 hover:bg-zinc-300 disabled:opacity-50"
>
Search
</button>
</form>
{error && <p className="text-sm text-red-400">{error}</p>}
{loading && <p className="text-sm text-zinc-400">Searching</p>}
<SearchResults />
</div>
);
}
+29
View File
@@ -0,0 +1,29 @@
import { create } from 'zustand';
import type { TTSMod } from '@tts/shared';
import { fetchMod } from '../api';
interface ModState {
id: string | null;
mod: TTSMod | null;
loading: boolean;
error: string | null;
load: (id: string) => Promise<void>;
clear: () => void;
}
export const useModStore = create<ModState>((set) => ({
id: null,
mod: null,
loading: false,
error: null,
load: async (id) => {
set({ loading: true, error: null, id });
try {
const mod = await fetchMod(id);
set({ mod, loading: false });
} catch (err) {
set({ loading: false, error: String(err) });
}
},
clear: () => set({ id: null, mod: null, error: null, loading: false }),
}));
+58
View File
@@ -0,0 +1,58 @@
import { afterEach, describe, expect, it, vi } from 'vitest';
import type { SearchResult } from '@tts/shared';
import { useSearchStore } from './searchStore';
const result: SearchResult = {
items: [
{
id: '1',
title: 'Wingspan',
author: 'someone',
previewImageUrl: 'https://example.com/p.png',
},
],
page: 1,
hasMore: false,
};
afterEach(() => {
vi.unstubAllGlobals();
useSearchStore.setState({
query: '',
page: 1,
items: [],
hasMore: false,
loading: false,
error: null,
});
});
describe('searchStore', () => {
it('stores results from a successful search', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(JSON.stringify(result))));
await useSearchStore.getState().search('wingspan');
const state = useSearchStore.getState();
expect(state.items).toHaveLength(1);
expect(state.items[0]!.title).toBe('Wingspan');
expect(state.loading).toBe(false);
expect(state.error).toBeNull();
});
it('records an error on failure', async () => {
vi.stubGlobal(
'fetch',
vi.fn().mockResolvedValue(new Response(JSON.stringify({ error: 'boom' }), { status: 502 })),
);
await useSearchStore.getState().search('wingspan');
const state = useSearchStore.getState();
expect(state.error).toContain('boom');
expect(state.items).toHaveLength(0);
});
it('ignores empty queries', async () => {
const fetchMock = vi.fn();
vi.stubGlobal('fetch', fetchMock);
await useSearchStore.getState().search(' ');
expect(fetchMock).not.toHaveBeenCalled();
});
});
+36
View File
@@ -0,0 +1,36 @@
import { create } from 'zustand';
import type { SearchResult, WorkshopItem } from '@tts/shared';
import { searchWorkshop } from '../api';
interface SearchState {
query: string;
page: number;
items: WorkshopItem[];
hasMore: boolean;
loading: boolean;
error: string | null;
search: (query: string, page?: number) => Promise<void>;
}
export const useSearchStore = create<SearchState>((set) => ({
query: '',
page: 1,
items: [],
hasMore: false,
loading: false,
error: null,
search: async (query, page = 1) => {
if (!query.trim()) return;
set({ loading: true, error: null, query, page });
try {
const result: SearchResult = await searchWorkshop(query, page);
set({
items: result.items,
hasMore: result.hasMore,
loading: false,
});
} catch (err) {
set({ loading: false, error: String(err) });
}
},
}));
+1
View File
@@ -0,0 +1 @@
/// <reference types="vite/client" />
+10
View File
@@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"noEmit": true,
"types": ["vite/client"]
},
"include": ["src"]
}
+15
View File
@@ -0,0 +1,15 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
plugins: [react(), tailwindcss()],
server: {
// Proxy API calls to the Hono backend during development.
proxy: {
'/search': 'http://localhost:3000',
'/items': 'http://localhost:3000',
'/health': 'http://localhost:3000',
},
},
});
+14 -5
View File
@@ -27,6 +27,7 @@ shared types/validation package.
| Package | Role | Runtime | | Package | Role | Runtime |
| ------------------ | ------------------------------------------- | ------------ | | ------------------ | ------------------------------------------- | ------------ |
| `apps/web` | React frontend: search + mod pages | Browser |
| `apps/proxy` | Hono HTTP server: search + fetch endpoints | Node | | `apps/proxy` | Hono HTTP server: search + fetch endpoints | Node |
| `packages/tts` | Fetch save from Steam, BSON-parse to `TTSMod` | Node | | `packages/tts` | Fetch save from Steam, BSON-parse to `TTSMod` | Node |
| `packages/extract` | Analyze a `TTSMod`: objects, refs, assets | Isomorphic | | `packages/extract` | Analyze a `TTSMod`: objects, refs, assets | Isomorphic |
@@ -35,15 +36,19 @@ shared types/validation package.
## Dependency graph ## Dependency graph
``` ```
apps/proxy ──► packages/tts ──► packages/shared apps/web ──► apps/proxy ──► packages/tts ──► packages/shared
│ │ │ │
│ └──► (fetchMod → TTSMod) └──► (fetchMod → TTSMod)
packages/extract ──► packages/tts (traverseMod) ──► packages/shared (types) └──► packages/extract ──► packages/shared (types)
``` ```
### Edges ### Edges
- **`apps/web``apps/proxy`** — calls `/search`, `/items/:id`, and
`/items/:id/file` over HTTP.
- **`apps/web``packages/extract`** — uses `flattenObjects` / `collectRefs`
to analyze a loaded `TTSMod` in the browser.
- **`apps/proxy``packages/tts`** — calls `fetchMod` / `getFileName` to serve - **`apps/proxy``packages/tts`** — calls `fetchMod` / `getFileName` to serve
item requests. item requests.
- **`apps/proxy``packages/shared`** — uses shared types and zod schemas for - **`apps/proxy``packages/shared`** — uses shared types and zod schemas for
@@ -90,11 +95,15 @@ packages/extract ──► packages/tts (traverseMod) ──► packages/shared
- `typescript` (strict), `tsx` (dev runner), `eslint`, `prettier`. - `typescript` (strict), `tsx` (dev runner), `eslint`, `prettier`.
- `vitest` (unit tests), colocated as `*.test.ts` next to sources. - `vitest` (unit tests), colocated as `*.test.ts` next to sources.
- `vite`, `@vitejs/plugin-react`, `tailwindcss`, `@tailwindcss/vite` — frontend
build/dev tooling.
- `pnpm` workspaces for package management. - `pnpm` workspaces for package management.
## Deployment / runtime shape ## Deployment / runtime shape
- The proxy runs as a single Node process via `@hono/node-server`. - The proxy runs as a single Node process via `@hono/node-server`.
- `apps/web` is a static Vite build served separately; during development it
proxies `/search`, `/items`, and `/health` to the proxy.
- `packages/extract` is published/consumed as a plain ESM module usable from a - `packages/extract` is published/consumed as a plain ESM module usable from a
browser bundle or Node. browser bundle or Node.
- No shared state between requests; each request fetches fresh. - No shared state between requests; each request fetches fresh.
+17 -1
View File
@@ -95,4 +95,20 @@ available in Node.
`<img>` / `<object>` elements. `<img>` / `<object>` elements.
**Alternatives considered:** Raw `ArrayBuffer`. Rejected — less convenient for **Alternatives considered:** Raw `ArrayBuffer`. Rejected — less convenient for
frontend rendering. frontend rendering.
## D9 — Frontend stack: React + React Router + Tailwind v4 + Zustand
**Decision:** The frontend (`apps/web`) uses React with React Router for
routing, Tailwind CSS v4 for styling, and Zustand for state, built with Vite.
**Context:** The frontend consumes the proxy API for search/fetch and
`packages/extract` directly for analysis. React is the de-facto standard for
this kind of tool; React Router provides declarative routes (`/` and
`/mod/:id`); Tailwind v4 is the current major version and integrates via
`@tailwindcss/vite`; Zustand is a minimal, unopinionated store that fits the
small amount of client state (search + mod).
**Alternatives considered:** Next.js (heavier than needed for a client-only
tool); Redux Toolkit (more boilerplate than warranted); CSS Modules (no
utility styling).
+60 -17
View File
@@ -21,20 +21,20 @@ Steam Workshop, fetching full TTS save files, and analyzing their contents.
## Non-goals (for now) ## Non-goals (for now)
- Backend traversal endpoints (deferred by design). - Backend traversal endpoints (deferred by design).
- Frontend app (later; will consume `packages/extract` directly).
- Caching / Redis / multi-instance concerns. - Caching / Redis / multi-instance concerns.
## Architecture ## Architecture
``` ```
apps/proxy ──► packages/tts ──► packages/shared apps/web ──► apps/proxy ──► packages/tts ──► packages/shared
│ │ │ │
│ └──► (fetchMod → TTSMod) └──► (fetchMod → TTSMod)
packages/extract ──► packages/tts (traverseMod) ──► packages/shared (types) └──► packages/extract ──► packages/shared (types)
(isomorphic, used by future frontend) (isomorphic, used by the frontend)
``` ```
- **`apps/web`** — React frontend (search + mod pages).
- **`apps/proxy`** — Hono server. Search + fetch only. No traversal endpoints. - **`apps/proxy`** — Hono server. Search + fetch only. No traversal endpoints.
- **`packages/tts`** — low-level fetcher: Steam API call, BSON parse, filename - **`packages/tts`** — low-level fetcher: Steam API call, BSON parse, filename
derivation. Returns raw parsed `TTSMod`. derivation. Returns raw parsed `TTSMod`.
@@ -54,16 +54,34 @@ tts-workshop/
├── docs/ ├── docs/
│ └── implementation-plan.md # this file │ └── implementation-plan.md # this file
├── apps/ ├── apps/
── proxy/ ── proxy/
│ │ ├── package.json
│ │ ├── tsconfig.json
│ │ └── src/
│ │ ├── index.ts # Hono app + @hono/node-server
│ │ ├── routes/
│ │ │ ├── search.ts # GET /search?q=...&page=1
│ │ │ ├── items.ts # GET /items/:id, /items/:id/file
│ │ │ └── health.ts # GET /health
│ │ └── env.ts # zod env validation
│ └── web/
│ ├── package.json │ ├── package.json
│ ├── tsconfig.json │ ├── tsconfig.json
│ ├── vite.config.ts # dev proxy → localhost:3000
│ ├── index.html
│ └── src/ │ └── src/
│ ├── index.ts # Hono app + @hono/node-server │ ├── main.tsx # React root + router
│ ├── routes/ │ ├── App.tsx # layout + routes
│ ├── search.ts # GET /search?q=...&page=1 ├── api.ts # fetch wrappers for /search, /items
├── items.ts # GET /items/:id, /items/:id/file │ ├── index.css # tailwind v4 entry
│ └── health.ts # GET /health ├── pages/
└── env.ts # zod env validation │ ├── SearchPage.tsx
│ │ └── ModPage.tsx
│ ├── components/
│ │ └── SearchResults.tsx
│ └── stores/
│ ├── searchStore.ts # zustand
│ └── modStore.ts # zustand
└── packages/ └── packages/
├── shared/ ├── shared/
│ └── src/ │ └── src/
@@ -159,6 +177,27 @@ Hono server exposing search + fetch.
- `GET /health` — liveness. - `GET /health` — liveness.
- `env.ts` — zod validation of `STEAM_API_KEY`, `PORT`. - `env.ts` — zod validation of `STEAM_API_KEY`, `PORT`.
### `apps/web`
React frontend (Vite + React Router + Tailwind v4 + Zustand). Consumes the
proxy API and `packages/extract` directly for analysis.
- `main.tsx` — React root, `BrowserRouter`.
- `App.tsx` — app shell (header) + routes: `/` (search), `/mod/:id` (mod).
- `api.ts` — typed `fetch` wrappers for `/search` and `/items/:id`, plus a
`modFileUrl` helper for the raw-file download.
- `pages/SearchPage.tsx` — search form, drives `searchStore`.
- `pages/ModPage.tsx` — loads the mod via `modStore`, uses `@tts/extract`
(`flattenObjects`, `collectRefs`) to render objects and asset refs, and links
to the raw save file.
- `components/SearchResults.tsx` — result grid + pagination.
- `stores/searchStore.ts` / `stores/modStore.ts` — Zustand stores for search
and mod state.
- `vite.config.ts` — dev proxy for `/search`, `/items`, `/health` →
`http://localhost:3000`.
- Styling: Tailwind v4 via `@tailwindcss/vite`; `index.css` imports
`tailwindcss`.
## Endpoints ## Endpoints
| Method | Path | Description | Auth | | Method | Path | Description | Auth |
@@ -186,14 +225,16 @@ packages/extract → flatten objects / extract refs / download assets
- `bson` — BSON deserialization. - `bson` — BSON deserialization.
- `cheerio` — Workshop browse page scraping (backend only). - `cheerio` — Workshop browse page scraping (backend only).
- `zod` — validation. - `zod` — validation.
- `react`, `react-dom`, `react-router-dom`, `zustand` — frontend.
- `vite`, `@vitejs/plugin-react`, `tailwindcss`, `@tailwindcss/vite` — frontend tooling.
- `tsx`, `typescript`, `eslint`, `prettier` — tooling. - `tsx`, `typescript`, `eslint`, `prettier` — tooling.
## Tooling ## Tooling
- TypeScript strict mode. - TypeScript strict mode.
- `tsx` for dev, `tsc` for build. - `tsx` for dev, `tsc` for build; `vite` for the frontend dev server/build.
- `vitest` for unit tests, colocated as `*.test.ts` next to sources. - `vitest` for unit tests, colocated as `*.test.ts` next to sources.
- Root scripts: `pnpm dev`, `pnpm build`, `pnpm test`, `pnpm lint`. - Root scripts: `pnpm dev`, `pnpm dev:web`, `pnpm build`, `pnpm test`, `pnpm lint`.
## Build order ## Build order
@@ -203,7 +244,9 @@ packages/extract → flatten objects / extract refs / download assets
3. `packages/tts` — fetch + parse (existing scraper code). 3. `packages/tts` — fetch + parse (existing scraper code).
4. `packages/extract` — objects, refs, download. 4. `packages/extract` — objects, refs, download.
5. `apps/proxy` — search + items + health routes, env validation. 5. `apps/proxy` — search + items + health routes, env validation.
6. Wire up root scripts, `.env.example`, README. 6. `apps/web` — React frontend (search + mod pages), consuming the proxy and
`packages/extract`.
7. Wire up root scripts, `.env.example`, README.
## Risks / caveats ## Risks / caveats
+1
View File
@@ -5,6 +5,7 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "pnpm --filter @tts/proxy dev", "dev": "pnpm --filter @tts/proxy dev",
"dev:web": "pnpm --filter @tts/web dev",
"build": "pnpm -r build", "build": "pnpm -r build",
"lint": "pnpm -r lint", "lint": "pnpm -r lint",
"typecheck": "pnpm -r typecheck", "typecheck": "pnpm -r typecheck",
+525 -7
View File
@@ -13,7 +13,7 @@ importers:
version: 5.9.3 version: 5.9.3
vitest: vitest:
specifier: ^4.1.10 specifier: ^4.1.10
version: 4.1.10(@types/node@22.20.1)(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.11)) version: 4.1.10(@types/node@22.20.1)(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11))
apps/proxy: apps/proxy:
dependencies: dependencies:
@@ -43,6 +43,52 @@ importers:
specifier: ^5.7.2 specifier: ^5.7.2
version: 5.9.3 version: 5.9.3
apps/web:
dependencies:
'@tts/extract':
specifier: workspace:*
version: link:../../packages/extract
'@tts/shared':
specifier: workspace:*
version: link:../../packages/shared
react:
specifier: ^19.2.8
version: 19.2.8
react-dom:
specifier: ^19.2.8
version: 19.2.8(react@19.2.8)
react-router-dom:
specifier: ^7.18.2
version: 7.18.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
zustand:
specifier: ^5.0.14
version: 5.0.14(@types/react@19.2.18)(react@19.2.8)
devDependencies:
'@tailwindcss/vite':
specifier: ^4.3.3
version: 4.3.3(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11))
'@types/react':
specifier: ^19.2.18
version: 19.2.18
'@types/react-dom':
specifier: ^19.2.4
version: 19.2.4(@types/react@19.2.18)
'@vitejs/plugin-react':
specifier: ^6.0.5
version: 6.0.5(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11))
tailwindcss:
specifier: ^4.3.3
version: 4.3.3
typescript:
specifier: ^5.7.2
version: 5.9.3
vite:
specifier: ^8.2.1
version: 8.2.1(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11)
vitest:
specifier: ^4.1.10
version: 4.1.10(@types/node@22.20.1)(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11))
packages/extract: packages/extract:
dependencies: dependencies:
'@tts/shared': '@tts/shared':
@@ -240,9 +286,22 @@ packages:
peerDependencies: peerDependencies:
hono: ^4 hono: ^4
'@jridgewell/gen-mapping@0.3.13':
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
'@jridgewell/remapping@2.3.5':
resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
'@jridgewell/resolve-uri@3.1.2':
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
engines: {node: '>=6.0.0'}
'@jridgewell/sourcemap-codec@1.5.5': '@jridgewell/sourcemap-codec@1.5.5':
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
'@oxc-project/types@0.143.0': '@oxc-project/types@0.143.0':
resolution: {integrity: sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==} resolution: {integrity: sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==}
@@ -342,6 +401,100 @@ packages:
'@standard-schema/spec@1.1.0': '@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
'@tailwindcss/node@4.3.3':
resolution: {integrity: sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==}
'@tailwindcss/oxide-android-arm64@4.3.3':
resolution: {integrity: sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [android]
'@tailwindcss/oxide-darwin-arm64@4.3.3':
resolution: {integrity: sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [darwin]
'@tailwindcss/oxide-darwin-x64@4.3.3':
resolution: {integrity: sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==}
engines: {node: '>= 20'}
cpu: [x64]
os: [darwin]
'@tailwindcss/oxide-freebsd-x64@4.3.3':
resolution: {integrity: sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==}
engines: {node: '>= 20'}
cpu: [x64]
os: [freebsd]
'@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3':
resolution: {integrity: sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==}
engines: {node: '>= 20'}
cpu: [arm]
os: [linux]
'@tailwindcss/oxide-linux-arm64-gnu@4.3.3':
resolution: {integrity: sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@tailwindcss/oxide-linux-arm64-musl@4.3.3':
resolution: {integrity: sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@tailwindcss/oxide-linux-x64-gnu@4.3.3':
resolution: {integrity: sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==}
engines: {node: '>= 20'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@tailwindcss/oxide-linux-x64-musl@4.3.3':
resolution: {integrity: sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==}
engines: {node: '>= 20'}
cpu: [x64]
os: [linux]
libc: [musl]
'@tailwindcss/oxide-wasm32-wasi@4.3.3':
resolution: {integrity: sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==}
engines: {node: '>=14.0.0'}
cpu: [wasm32]
bundledDependencies:
- '@napi-rs/wasm-runtime'
- '@emnapi/core'
- '@emnapi/runtime'
- '@tybys/wasm-util'
- '@emnapi/wasi-threads'
- tslib
'@tailwindcss/oxide-win32-arm64-msvc@4.3.3':
resolution: {integrity: sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [win32]
'@tailwindcss/oxide-win32-x64-msvc@4.3.3':
resolution: {integrity: sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==}
engines: {node: '>= 20'}
cpu: [x64]
os: [win32]
'@tailwindcss/oxide@4.3.3':
resolution: {integrity: sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==}
engines: {node: '>= 20'}
'@tailwindcss/vite@4.3.3':
resolution: {integrity: sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==}
peerDependencies:
vite: ^5.2.0 || ^6 || ^7 || ^8
'@types/chai@5.2.3': '@types/chai@5.2.3':
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
@@ -354,6 +507,27 @@ packages:
'@types/node@22.20.1': '@types/node@22.20.1':
resolution: {integrity: sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==} resolution: {integrity: sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==}
'@types/react-dom@19.2.4':
resolution: {integrity: sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==}
peerDependencies:
'@types/react': ^19.2.0
'@types/react@19.2.18':
resolution: {integrity: sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==}
'@vitejs/plugin-react@6.0.5':
resolution: {integrity: sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==}
engines: {node: ^20.19.0 || >=22.12.0}
peerDependencies:
'@rolldown/plugin-babel': ^0.1.7 || ^0.2.0
babel-plugin-react-compiler: ^1.0.0
vite: ^8.0.0
peerDependenciesMeta:
'@rolldown/plugin-babel':
optional: true
babel-plugin-react-compiler:
optional: true
'@vitest/expect@4.1.10': '@vitest/expect@4.1.10':
resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==}
@@ -398,10 +572,21 @@ packages:
convert-source-map@2.0.0: convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
cookie@1.1.1:
resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
engines: {node: '>=18'}
csstype@3.2.3:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
detect-libc@2.1.2: detect-libc@2.1.2:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
enhanced-resolve@5.24.5:
resolution: {integrity: sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==}
engines: {node: '>=10.13.0'}
es-module-lexer@2.3.1: es-module-lexer@2.3.1:
resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==}
@@ -431,40 +616,84 @@ packages:
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin] os: [darwin]
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
hono@4.13.1: hono@4.13.1:
resolution: {integrity: sha512-kdJoFVv2xmayw6cY09H7AbMJMt8Jn5jdlEdXsP7AGBdF2DIptVlKlOLKXP41yPip4/a3yQPv9gVcJYI8YY04dw==} resolution: {integrity: sha512-kdJoFVv2xmayw6cY09H7AbMJMt8Jn5jdlEdXsP7AGBdF2DIptVlKlOLKXP41yPip4/a3yQPv9gVcJYI8YY04dw==}
engines: {node: '>=16.9.0'} engines: {node: '>=16.9.0'}
jiti@2.7.0:
resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==}
hasBin: true
lightningcss-android-arm64@1.32.0:
resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [android]
lightningcss-android-arm64@1.33.0: lightningcss-android-arm64@1.33.0:
resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
cpu: [arm64] cpu: [arm64]
os: [android] os: [android]
lightningcss-darwin-arm64@1.32.0:
resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [darwin]
lightningcss-darwin-arm64@1.33.0: lightningcss-darwin-arm64@1.33.0:
resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
lightningcss-darwin-x64@1.32.0:
resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [darwin]
lightningcss-darwin-x64@1.33.0: lightningcss-darwin-x64@1.33.0:
resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
lightningcss-freebsd-x64@1.32.0:
resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [freebsd]
lightningcss-freebsd-x64@1.33.0: lightningcss-freebsd-x64@1.33.0:
resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
cpu: [x64] cpu: [x64]
os: [freebsd] os: [freebsd]
lightningcss-linux-arm-gnueabihf@1.32.0:
resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==}
engines: {node: '>= 12.0.0'}
cpu: [arm]
os: [linux]
lightningcss-linux-arm-gnueabihf@1.33.0: lightningcss-linux-arm-gnueabihf@1.33.0:
resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
lightningcss-linux-arm64-gnu@1.32.0:
resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
lightningcss-linux-arm64-gnu@1.33.0: lightningcss-linux-arm64-gnu@1.33.0:
resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
@@ -472,6 +701,13 @@ packages:
os: [linux] os: [linux]
libc: [glibc] libc: [glibc]
lightningcss-linux-arm64-musl@1.32.0:
resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
lightningcss-linux-arm64-musl@1.33.0: lightningcss-linux-arm64-musl@1.33.0:
resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
@@ -479,6 +715,13 @@ packages:
os: [linux] os: [linux]
libc: [musl] libc: [musl]
lightningcss-linux-x64-gnu@1.32.0:
resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
lightningcss-linux-x64-gnu@1.33.0: lightningcss-linux-x64-gnu@1.33.0:
resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
@@ -486,6 +729,13 @@ packages:
os: [linux] os: [linux]
libc: [glibc] libc: [glibc]
lightningcss-linux-x64-musl@1.32.0:
resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [musl]
lightningcss-linux-x64-musl@1.33.0: lightningcss-linux-x64-musl@1.33.0:
resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
@@ -493,18 +743,34 @@ packages:
os: [linux] os: [linux]
libc: [musl] libc: [musl]
lightningcss-win32-arm64-msvc@1.32.0:
resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [win32]
lightningcss-win32-arm64-msvc@1.33.0: lightningcss-win32-arm64-msvc@1.33.0:
resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
lightningcss-win32-x64-msvc@1.32.0:
resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [win32]
lightningcss-win32-x64-msvc@1.33.0: lightningcss-win32-x64-msvc@1.33.0:
resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
lightningcss@1.32.0:
resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
engines: {node: '>= 12.0.0'}
lightningcss@1.33.0: lightningcss@1.33.0:
resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
@@ -535,11 +801,43 @@ packages:
resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==}
engines: {node: ^10 || ^12 || >=14} engines: {node: ^10 || ^12 || >=14}
react-dom@19.2.8:
resolution: {integrity: sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==}
peerDependencies:
react: ^19.2.8
react-router-dom@7.18.2:
resolution: {integrity: sha512-AIKJ/jgGlFb3EbfCXk5Gzshiwt+l3mqbCrNjmEWMMjqQxNJ3svBa6bgzFyCC2Sw3RA0VWF1kg3uQf2OFhxb8hw==}
engines: {node: '>=20.0.0'}
peerDependencies:
react: '>=18'
react-dom: '>=18'
react-router@7.18.2:
resolution: {integrity: sha512-aUVMjFm3GAPTTZL7oYr5E7ETiqfQCHRLH+B+5afnICvf0r7kkK4eR6SMuwbSTJw/7t+12khT/Kahij49fqOCIg==}
engines: {node: '>=20.0.0'}
peerDependencies:
react: '>=18'
react-dom: '>=18'
peerDependenciesMeta:
react-dom:
optional: true
react@19.2.8:
resolution: {integrity: sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==}
engines: {node: '>=0.10.0'}
rolldown@1.2.3: rolldown@1.2.3:
resolution: {integrity: sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==} resolution: {integrity: sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true hasBin: true
scheduler@0.27.0:
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
set-cookie-parser@2.7.2:
resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==}
siginfo@2.0.0: siginfo@2.0.0:
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
@@ -553,6 +851,13 @@ packages:
std-env@4.2.0: std-env@4.2.0:
resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==}
tailwindcss@4.3.3:
resolution: {integrity: sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==}
tapable@2.3.3:
resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==}
engines: {node: '>=6'}
tinybench@2.9.0: tinybench@2.9.0:
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
@@ -673,6 +978,24 @@ packages:
zod@3.25.76: zod@3.25.76:
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
zustand@5.0.14:
resolution: {integrity: sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==}
engines: {node: '>=12.20.0'}
peerDependencies:
'@types/react': '>=18.0.0'
immer: '>=9.0.6'
react: '>=18.0.0'
use-sync-external-store: '>=1.2.0'
peerDependenciesMeta:
'@types/react':
optional: true
immer:
optional: true
react:
optional: true
use-sync-external-store:
optional: true
snapshots: snapshots:
'@esbuild/aix-ppc64@0.28.1': '@esbuild/aix-ppc64@0.28.1':
@@ -757,8 +1080,25 @@ snapshots:
dependencies: dependencies:
hono: 4.13.1 hono: 4.13.1
'@jridgewell/gen-mapping@0.3.13':
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
'@jridgewell/trace-mapping': 0.3.31
'@jridgewell/remapping@2.3.5':
dependencies:
'@jridgewell/gen-mapping': 0.3.13
'@jridgewell/trace-mapping': 0.3.31
'@jridgewell/resolve-uri@3.1.2': {}
'@jridgewell/sourcemap-codec@1.5.5': {} '@jridgewell/sourcemap-codec@1.5.5': {}
'@jridgewell/trace-mapping@0.3.31':
dependencies:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
'@oxc-project/types@0.143.0': {} '@oxc-project/types@0.143.0': {}
'@rolldown/binding-android-arm64@1.2.3': '@rolldown/binding-android-arm64@1.2.3':
@@ -807,6 +1147,74 @@ snapshots:
'@standard-schema/spec@1.1.0': {} '@standard-schema/spec@1.1.0': {}
'@tailwindcss/node@4.3.3':
dependencies:
'@jridgewell/remapping': 2.3.5
enhanced-resolve: 5.24.5
jiti: 2.7.0
lightningcss: 1.32.0
magic-string: 0.30.21
source-map-js: 1.2.1
tailwindcss: 4.3.3
'@tailwindcss/oxide-android-arm64@4.3.3':
optional: true
'@tailwindcss/oxide-darwin-arm64@4.3.3':
optional: true
'@tailwindcss/oxide-darwin-x64@4.3.3':
optional: true
'@tailwindcss/oxide-freebsd-x64@4.3.3':
optional: true
'@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3':
optional: true
'@tailwindcss/oxide-linux-arm64-gnu@4.3.3':
optional: true
'@tailwindcss/oxide-linux-arm64-musl@4.3.3':
optional: true
'@tailwindcss/oxide-linux-x64-gnu@4.3.3':
optional: true
'@tailwindcss/oxide-linux-x64-musl@4.3.3':
optional: true
'@tailwindcss/oxide-wasm32-wasi@4.3.3':
optional: true
'@tailwindcss/oxide-win32-arm64-msvc@4.3.3':
optional: true
'@tailwindcss/oxide-win32-x64-msvc@4.3.3':
optional: true
'@tailwindcss/oxide@4.3.3':
optionalDependencies:
'@tailwindcss/oxide-android-arm64': 4.3.3
'@tailwindcss/oxide-darwin-arm64': 4.3.3
'@tailwindcss/oxide-darwin-x64': 4.3.3
'@tailwindcss/oxide-freebsd-x64': 4.3.3
'@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.3
'@tailwindcss/oxide-linux-arm64-gnu': 4.3.3
'@tailwindcss/oxide-linux-arm64-musl': 4.3.3
'@tailwindcss/oxide-linux-x64-gnu': 4.3.3
'@tailwindcss/oxide-linux-x64-musl': 4.3.3
'@tailwindcss/oxide-wasm32-wasi': 4.3.3
'@tailwindcss/oxide-win32-arm64-msvc': 4.3.3
'@tailwindcss/oxide-win32-x64-msvc': 4.3.3
'@tailwindcss/vite@4.3.3(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11))':
dependencies:
'@tailwindcss/node': 4.3.3
'@tailwindcss/oxide': 4.3.3
tailwindcss: 4.3.3
vite: 8.2.1(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11)
'@types/chai@5.2.3': '@types/chai@5.2.3':
dependencies: dependencies:
'@types/deep-eql': 4.0.2 '@types/deep-eql': 4.0.2
@@ -820,6 +1228,19 @@ snapshots:
dependencies: dependencies:
undici-types: 6.21.0 undici-types: 6.21.0
'@types/react-dom@19.2.4(@types/react@19.2.18)':
dependencies:
'@types/react': 19.2.18
'@types/react@19.2.18':
dependencies:
csstype: 3.2.3
'@vitejs/plugin-react@6.0.5(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11))':
dependencies:
'@rolldown/pluginutils': 1.0.1
vite: 8.2.1(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11)
'@vitest/expect@4.1.10': '@vitest/expect@4.1.10':
dependencies: dependencies:
'@standard-schema/spec': 1.1.0 '@standard-schema/spec': 1.1.0
@@ -829,13 +1250,13 @@ snapshots:
chai: 6.2.2 chai: 6.2.2
tinyrainbow: 3.1.1 tinyrainbow: 3.1.1
'@vitest/mocker@4.1.10(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.11))': '@vitest/mocker@4.1.10(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11))':
dependencies: dependencies:
'@vitest/spy': 4.1.10 '@vitest/spy': 4.1.10
estree-walker: 3.0.3 estree-walker: 3.0.3
magic-string: 0.30.21 magic-string: 0.30.21
optionalDependencies: optionalDependencies:
vite: 8.2.1(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.11) vite: 8.2.1(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11)
'@vitest/pretty-format@4.1.10': '@vitest/pretty-format@4.1.10':
dependencies: dependencies:
@@ -869,8 +1290,17 @@ snapshots:
convert-source-map@2.0.0: {} convert-source-map@2.0.0: {}
cookie@1.1.1: {}
csstype@3.2.3: {}
detect-libc@2.1.2: {} detect-libc@2.1.2: {}
enhanced-resolve@5.24.5:
dependencies:
graceful-fs: 4.2.11
tapable: 2.3.3
es-module-lexer@2.3.1: {} es-module-lexer@2.3.1: {}
esbuild@0.28.1: esbuild@0.28.1:
@@ -915,41 +1345,94 @@ snapshots:
fsevents@2.3.3: fsevents@2.3.3:
optional: true optional: true
graceful-fs@4.2.11: {}
hono@4.13.1: {} hono@4.13.1: {}
jiti@2.7.0: {}
lightningcss-android-arm64@1.32.0:
optional: true
lightningcss-android-arm64@1.33.0: lightningcss-android-arm64@1.33.0:
optional: true optional: true
lightningcss-darwin-arm64@1.32.0:
optional: true
lightningcss-darwin-arm64@1.33.0: lightningcss-darwin-arm64@1.33.0:
optional: true optional: true
lightningcss-darwin-x64@1.32.0:
optional: true
lightningcss-darwin-x64@1.33.0: lightningcss-darwin-x64@1.33.0:
optional: true optional: true
lightningcss-freebsd-x64@1.32.0:
optional: true
lightningcss-freebsd-x64@1.33.0: lightningcss-freebsd-x64@1.33.0:
optional: true optional: true
lightningcss-linux-arm-gnueabihf@1.32.0:
optional: true
lightningcss-linux-arm-gnueabihf@1.33.0: lightningcss-linux-arm-gnueabihf@1.33.0:
optional: true optional: true
lightningcss-linux-arm64-gnu@1.32.0:
optional: true
lightningcss-linux-arm64-gnu@1.33.0: lightningcss-linux-arm64-gnu@1.33.0:
optional: true optional: true
lightningcss-linux-arm64-musl@1.32.0:
optional: true
lightningcss-linux-arm64-musl@1.33.0: lightningcss-linux-arm64-musl@1.33.0:
optional: true optional: true
lightningcss-linux-x64-gnu@1.32.0:
optional: true
lightningcss-linux-x64-gnu@1.33.0: lightningcss-linux-x64-gnu@1.33.0:
optional: true optional: true
lightningcss-linux-x64-musl@1.32.0:
optional: true
lightningcss-linux-x64-musl@1.33.0: lightningcss-linux-x64-musl@1.33.0:
optional: true optional: true
lightningcss-win32-arm64-msvc@1.32.0:
optional: true
lightningcss-win32-arm64-msvc@1.33.0: lightningcss-win32-arm64-msvc@1.33.0:
optional: true optional: true
lightningcss-win32-x64-msvc@1.32.0:
optional: true
lightningcss-win32-x64-msvc@1.33.0: lightningcss-win32-x64-msvc@1.33.0:
optional: true optional: true
lightningcss@1.32.0:
dependencies:
detect-libc: 2.1.2
optionalDependencies:
lightningcss-android-arm64: 1.32.0
lightningcss-darwin-arm64: 1.32.0
lightningcss-darwin-x64: 1.32.0
lightningcss-freebsd-x64: 1.32.0
lightningcss-linux-arm-gnueabihf: 1.32.0
lightningcss-linux-arm64-gnu: 1.32.0
lightningcss-linux-arm64-musl: 1.32.0
lightningcss-linux-x64-gnu: 1.32.0
lightningcss-linux-x64-musl: 1.32.0
lightningcss-win32-arm64-msvc: 1.32.0
lightningcss-win32-x64-msvc: 1.32.0
lightningcss@1.33.0: lightningcss@1.33.0:
dependencies: dependencies:
detect-libc: 2.1.2 detect-libc: 2.1.2
@@ -986,6 +1469,27 @@ snapshots:
picocolors: 1.1.1 picocolors: 1.1.1
source-map-js: 1.2.1 source-map-js: 1.2.1
react-dom@19.2.8(react@19.2.8):
dependencies:
react: 19.2.8
scheduler: 0.27.0
react-router-dom@7.18.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8):
dependencies:
react: 19.2.8
react-dom: 19.2.8(react@19.2.8)
react-router: 7.18.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
react-router@7.18.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8):
dependencies:
cookie: 1.1.1
react: 19.2.8
set-cookie-parser: 2.7.2
optionalDependencies:
react-dom: 19.2.8(react@19.2.8)
react@19.2.8: {}
rolldown@1.2.3: rolldown@1.2.3:
dependencies: dependencies:
'@oxc-project/types': 0.143.0 '@oxc-project/types': 0.143.0
@@ -1006,6 +1510,10 @@ snapshots:
'@rolldown/binding-win32-arm64-msvc': 1.2.3 '@rolldown/binding-win32-arm64-msvc': 1.2.3
'@rolldown/binding-win32-x64-msvc': 1.2.3 '@rolldown/binding-win32-x64-msvc': 1.2.3
scheduler@0.27.0: {}
set-cookie-parser@2.7.2: {}
siginfo@2.0.0: {} siginfo@2.0.0: {}
source-map-js@1.2.1: {} source-map-js@1.2.1: {}
@@ -1014,6 +1522,10 @@ snapshots:
std-env@4.2.0: {} std-env@4.2.0: {}
tailwindcss@4.3.3: {}
tapable@2.3.3: {}
tinybench@2.9.0: {} tinybench@2.9.0: {}
tinyexec@1.3.0: {} tinyexec@1.3.0: {}
@@ -1035,7 +1547,7 @@ snapshots:
undici-types@6.21.0: {} undici-types@6.21.0: {}
vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.11): vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11):
dependencies: dependencies:
lightningcss: 1.33.0 lightningcss: 1.33.0
picomatch: 4.0.5 picomatch: 4.0.5
@@ -1046,12 +1558,13 @@ snapshots:
'@types/node': 22.20.1 '@types/node': 22.20.1
esbuild: 0.28.1 esbuild: 0.28.1
fsevents: 2.3.3 fsevents: 2.3.3
jiti: 2.7.0
tsx: 4.23.11 tsx: 4.23.11
vitest@4.1.10(@types/node@22.20.1)(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.11)): vitest@4.1.10(@types/node@22.20.1)(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11)):
dependencies: dependencies:
'@vitest/expect': 4.1.10 '@vitest/expect': 4.1.10
'@vitest/mocker': 4.1.10(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.11)) '@vitest/mocker': 4.1.10(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11))
'@vitest/pretty-format': 4.1.10 '@vitest/pretty-format': 4.1.10
'@vitest/runner': 4.1.10 '@vitest/runner': 4.1.10
'@vitest/snapshot': 4.1.10 '@vitest/snapshot': 4.1.10
@@ -1068,7 +1581,7 @@ snapshots:
tinyexec: 1.3.0 tinyexec: 1.3.0
tinyglobby: 0.2.17 tinyglobby: 0.2.17
tinyrainbow: 3.1.1 tinyrainbow: 3.1.1
vite: 8.2.1(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.11) vite: 8.2.1(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.11)
why-is-node-running: 2.3.0 why-is-node-running: 2.3.0
optionalDependencies: optionalDependencies:
'@types/node': 22.20.1 '@types/node': 22.20.1
@@ -1081,3 +1594,8 @@ snapshots:
stackback: 0.0.2 stackback: 0.0.2
zod@3.25.76: {} zod@3.25.76: {}
zustand@5.0.14(@types/react@19.2.18)(react@19.2.8):
optionalDependencies:
'@types/react': 19.2.18
react: 19.2.8