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:
@@ -7,6 +7,7 @@ analyze their contents. A lightweight, client-only pnpm monorepo.
|
||||
|
||||
| Package | Role | Runtime |
|
||||
| ------------------- | ------------------------------------------------------ | ---------- |
|
||||
| `apps/web` | React frontend: search + mod pages | Browser |
|
||||
| `apps/proxy` | Hono HTTP server: Workshop search + save fetch | Node |
|
||||
| `packages/tts` | Fetch save from Steam, BSON-parse to `TTSMod` | Node |
|
||||
| `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).
|
||||
|
||||
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
|
||||
|
||||
| Method | Path | Description |
|
||||
@@ -38,6 +43,7 @@ Get a Steam Web API key at https://steamcommunity.com/dev/apikey (free).
|
||||
|
||||
```sh
|
||||
pnpm dev # run the proxy (tsx watch)
|
||||
pnpm dev:web # run the frontend (vite)
|
||||
pnpm build # compile all packages
|
||||
pnpm typecheck # typecheck all packages
|
||||
pnpm test # run the unit tests (vitest)
|
||||
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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`;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
@import "tailwindcss";
|
||||
@@ -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>,
|
||||
);
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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 }),
|
||||
}));
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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) });
|
||||
}
|
||||
},
|
||||
}));
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"jsx": "react-jsx",
|
||||
"noEmit": true,
|
||||
"types": ["vite/client"]
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -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
@@ -27,6 +27,7 @@ shared types/validation package.
|
||||
|
||||
| Package | Role | Runtime |
|
||||
| ------------------ | ------------------------------------------- | ------------ |
|
||||
| `apps/web` | React frontend: search + mod pages | Browser |
|
||||
| `apps/proxy` | Hono HTTP server: search + fetch endpoints | Node |
|
||||
| `packages/tts` | Fetch save from Steam, BSON-parse to `TTSMod` | Node |
|
||||
| `packages/extract` | Analyze a `TTSMod`: objects, refs, assets | Isomorphic |
|
||||
@@ -35,15 +36,19 @@ shared types/validation package.
|
||||
## Dependency graph
|
||||
|
||||
```
|
||||
apps/proxy ──► packages/tts ──► packages/shared
|
||||
│ │
|
||||
│ └──► (fetchMod → TTSMod)
|
||||
▼
|
||||
packages/extract ──► packages/tts (traverseMod) ──► packages/shared (types)
|
||||
apps/web ──► apps/proxy ──► packages/tts ──► packages/shared
|
||||
│ │ │
|
||||
│ │ └──► (fetchMod → TTSMod)
|
||||
│ ▼
|
||||
└──► packages/extract ──► packages/shared (types)
|
||||
```
|
||||
|
||||
### 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
|
||||
item requests.
|
||||
- **`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`.
|
||||
- `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.
|
||||
|
||||
## Deployment / runtime shape
|
||||
|
||||
- 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
|
||||
browser bundle or Node.
|
||||
- No shared state between requests; each request fetches fresh.
|
||||
+17
-1
@@ -95,4 +95,20 @@ available in Node.
|
||||
`<img>` / `<object>` elements.
|
||||
|
||||
**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
@@ -21,20 +21,20 @@ Steam Workshop, fetching full TTS save files, and analyzing their contents.
|
||||
## Non-goals (for now)
|
||||
|
||||
- Backend traversal endpoints (deferred by design).
|
||||
- Frontend app (later; will consume `packages/extract` directly).
|
||||
- Caching / Redis / multi-instance concerns.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
apps/proxy ──► packages/tts ──► packages/shared
|
||||
│ │
|
||||
│ └──► (fetchMod → TTSMod)
|
||||
▼
|
||||
packages/extract ──► packages/tts (traverseMod) ──► packages/shared (types)
|
||||
(isomorphic, used by future frontend)
|
||||
apps/web ──► apps/proxy ──► packages/tts ──► packages/shared
|
||||
│ │ │
|
||||
│ │ └──► (fetchMod → TTSMod)
|
||||
│ ▼
|
||||
└──► packages/extract ──► packages/shared (types)
|
||||
(isomorphic, used by the frontend)
|
||||
```
|
||||
|
||||
- **`apps/web`** — React frontend (search + mod pages).
|
||||
- **`apps/proxy`** — Hono server. Search + fetch only. No traversal endpoints.
|
||||
- **`packages/tts`** — low-level fetcher: Steam API call, BSON parse, filename
|
||||
derivation. Returns raw parsed `TTSMod`.
|
||||
@@ -54,16 +54,34 @@ tts-workshop/
|
||||
├── docs/
|
||||
│ └── implementation-plan.md # this file
|
||||
├── 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
|
||||
│ ├── tsconfig.json
|
||||
│ ├── vite.config.ts # dev proxy → localhost:3000
|
||||
│ ├── index.html
|
||||
│ └── 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
|
||||
│ ├── main.tsx # React root + router
|
||||
│ ├── App.tsx # layout + routes
|
||||
│ ├── api.ts # fetch wrappers for /search, /items
|
||||
│ ├── index.css # tailwind v4 entry
|
||||
│ ├── pages/
|
||||
│ │ ├── SearchPage.tsx
|
||||
│ │ └── ModPage.tsx
|
||||
│ ├── components/
|
||||
│ │ └── SearchResults.tsx
|
||||
│ └── stores/
|
||||
│ ├── searchStore.ts # zustand
|
||||
│ └── modStore.ts # zustand
|
||||
└── packages/
|
||||
├── shared/
|
||||
│ └── src/
|
||||
@@ -159,6 +177,27 @@ Hono server exposing search + fetch.
|
||||
- `GET /health` — liveness.
|
||||
- `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
|
||||
|
||||
| Method | Path | Description | Auth |
|
||||
@@ -186,14 +225,16 @@ packages/extract → flatten objects / extract refs / download assets
|
||||
- `bson` — BSON deserialization.
|
||||
- `cheerio` — Workshop browse page scraping (backend only).
|
||||
- `zod` — validation.
|
||||
- `react`, `react-dom`, `react-router-dom`, `zustand` — frontend.
|
||||
- `vite`, `@vitejs/plugin-react`, `tailwindcss`, `@tailwindcss/vite` — frontend tooling.
|
||||
- `tsx`, `typescript`, `eslint`, `prettier` — tooling.
|
||||
|
||||
## Tooling
|
||||
|
||||
- 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.
|
||||
- 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
|
||||
|
||||
@@ -203,7 +244,9 @@ packages/extract → flatten objects / extract refs / download assets
|
||||
3. `packages/tts` — fetch + parse (existing scraper code).
|
||||
4. `packages/extract` — objects, refs, download.
|
||||
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
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "pnpm --filter @tts/proxy dev",
|
||||
"dev:web": "pnpm --filter @tts/web dev",
|
||||
"build": "pnpm -r build",
|
||||
"lint": "pnpm -r lint",
|
||||
"typecheck": "pnpm -r typecheck",
|
||||
|
||||
Generated
+525
-7
@@ -13,7 +13,7 @@ importers:
|
||||
version: 5.9.3
|
||||
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)(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:
|
||||
dependencies:
|
||||
@@ -43,6 +43,52 @@ importers:
|
||||
specifier: ^5.7.2
|
||||
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:
|
||||
dependencies:
|
||||
'@tts/shared':
|
||||
@@ -240,9 +286,22 @@ packages:
|
||||
peerDependencies:
|
||||
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':
|
||||
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.31':
|
||||
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
||||
|
||||
'@oxc-project/types@0.143.0':
|
||||
resolution: {integrity: sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==}
|
||||
|
||||
@@ -342,6 +401,100 @@ packages:
|
||||
'@standard-schema/spec@1.1.0':
|
||||
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':
|
||||
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
|
||||
|
||||
@@ -354,6 +507,27 @@ packages:
|
||||
'@types/node@22.20.1':
|
||||
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':
|
||||
resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==}
|
||||
|
||||
@@ -398,10 +572,21 @@ packages:
|
||||
convert-source-map@2.0.0:
|
||||
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:
|
||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
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:
|
||||
resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==}
|
||||
|
||||
@@ -431,40 +616,84 @@ packages:
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
graceful-fs@4.2.11:
|
||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||
|
||||
hono@4.13.1:
|
||||
resolution: {integrity: sha512-kdJoFVv2xmayw6cY09H7AbMJMt8Jn5jdlEdXsP7AGBdF2DIptVlKlOLKXP41yPip4/a3yQPv9gVcJYI8YY04dw==}
|
||||
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:
|
||||
resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
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:
|
||||
resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
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:
|
||||
resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
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:
|
||||
resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
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:
|
||||
resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm]
|
||||
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:
|
||||
resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
@@ -472,6 +701,13 @@ packages:
|
||||
os: [linux]
|
||||
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:
|
||||
resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
@@ -479,6 +715,13 @@ packages:
|
||||
os: [linux]
|
||||
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:
|
||||
resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
@@ -486,6 +729,13 @@ packages:
|
||||
os: [linux]
|
||||
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:
|
||||
resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
@@ -493,18 +743,34 @@ packages:
|
||||
os: [linux]
|
||||
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:
|
||||
resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
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:
|
||||
resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
lightningcss@1.32.0:
|
||||
resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
|
||||
lightningcss@1.33.0:
|
||||
resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
@@ -535,11 +801,43 @@ packages:
|
||||
resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==}
|
||||
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:
|
||||
resolution: {integrity: sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
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:
|
||||
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
|
||||
|
||||
@@ -553,6 +851,13 @@ packages:
|
||||
std-env@4.2.0:
|
||||
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:
|
||||
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
||||
|
||||
@@ -673,6 +978,24 @@ packages:
|
||||
zod@3.25.76:
|
||||
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:
|
||||
|
||||
'@esbuild/aix-ppc64@0.28.1':
|
||||
@@ -757,8 +1080,25 @@ snapshots:
|
||||
dependencies:
|
||||
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/trace-mapping@0.3.31':
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
'@oxc-project/types@0.143.0': {}
|
||||
|
||||
'@rolldown/binding-android-arm64@1.2.3':
|
||||
@@ -807,6 +1147,74 @@ snapshots:
|
||||
|
||||
'@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':
|
||||
dependencies:
|
||||
'@types/deep-eql': 4.0.2
|
||||
@@ -820,6 +1228,19 @@ snapshots:
|
||||
dependencies:
|
||||
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':
|
||||
dependencies:
|
||||
'@standard-schema/spec': 1.1.0
|
||||
@@ -829,13 +1250,13 @@ snapshots:
|
||||
chai: 6.2.2
|
||||
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:
|
||||
'@vitest/spy': 4.1.10
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.21
|
||||
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':
|
||||
dependencies:
|
||||
@@ -869,8 +1290,17 @@ snapshots:
|
||||
|
||||
convert-source-map@2.0.0: {}
|
||||
|
||||
cookie@1.1.1: {}
|
||||
|
||||
csstype@3.2.3: {}
|
||||
|
||||
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: {}
|
||||
|
||||
esbuild@0.28.1:
|
||||
@@ -915,41 +1345,94 @@ snapshots:
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
graceful-fs@4.2.11: {}
|
||||
|
||||
hono@4.13.1: {}
|
||||
|
||||
jiti@2.7.0: {}
|
||||
|
||||
lightningcss-android-arm64@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-android-arm64@1.33.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-darwin-arm64@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-darwin-arm64@1.33.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-darwin-x64@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-darwin-x64@1.33.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-freebsd-x64@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-freebsd-x64@1.33.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-arm-gnueabihf@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-arm-gnueabihf@1.33.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-arm64-gnu@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-arm64-gnu@1.33.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-arm64-musl@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-arm64-musl@1.33.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-x64-gnu@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-x64-gnu@1.33.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-x64-musl@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-x64-musl@1.33.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.33.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-win32-x64-msvc@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-win32-x64-msvc@1.33.0:
|
||||
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:
|
||||
dependencies:
|
||||
detect-libc: 2.1.2
|
||||
@@ -986,6 +1469,27 @@ snapshots:
|
||||
picocolors: 1.1.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:
|
||||
dependencies:
|
||||
'@oxc-project/types': 0.143.0
|
||||
@@ -1006,6 +1510,10 @@ snapshots:
|
||||
'@rolldown/binding-win32-arm64-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: {}
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
@@ -1014,6 +1522,10 @@ snapshots:
|
||||
|
||||
std-env@4.2.0: {}
|
||||
|
||||
tailwindcss@4.3.3: {}
|
||||
|
||||
tapable@2.3.3: {}
|
||||
|
||||
tinybench@2.9.0: {}
|
||||
|
||||
tinyexec@1.3.0: {}
|
||||
@@ -1035,7 +1547,7 @@ snapshots:
|
||||
|
||||
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:
|
||||
lightningcss: 1.33.0
|
||||
picomatch: 4.0.5
|
||||
@@ -1046,12 +1558,13 @@ snapshots:
|
||||
'@types/node': 22.20.1
|
||||
esbuild: 0.28.1
|
||||
fsevents: 2.3.3
|
||||
jiti: 2.7.0
|
||||
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:
|
||||
'@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/runner': 4.1.10
|
||||
'@vitest/snapshot': 4.1.10
|
||||
@@ -1068,7 +1581,7 @@ snapshots:
|
||||
tinyexec: 1.3.0
|
||||
tinyglobby: 0.2.17
|
||||
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
|
||||
optionalDependencies:
|
||||
'@types/node': 22.20.1
|
||||
@@ -1081,3 +1594,8 @@ snapshots:
|
||||
stackback: 0.0.2
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user