diff --git a/apps/web/index.html b/apps/web/index.html new file mode 100644 index 0000000..d5a789d --- /dev/null +++ b/apps/web/index.html @@ -0,0 +1,13 @@ + + + + + + + Multi SimAdmin + + +
+ + + diff --git a/apps/web/package.json b/apps/web/package.json index d37e9f2..f63ecab 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -5,6 +5,23 @@ "type": "module", "exports": "./src/index.ts", "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "test": "vitest run src", "typecheck": "tsc -p tsconfig.json" + }, + "dependencies": { + "react": "19.2.4", + "react-dom": "19.2.4" + }, + "devDependencies": { + "@testing-library/react": "16.3.0", + "@testing-library/user-event": "14.6.1", + "@types/react": "19.2.7", + "@types/react-dom": "19.2.3", + "@vitejs/plugin-react": "5.1.1", + "jsdom": "27.2.0", + "vite": "7.3.5" } } diff --git a/apps/web/src/app-shell.integration.test.tsx b/apps/web/src/app-shell.integration.test.tsx new file mode 100644 index 0000000..9a2fc42 --- /dev/null +++ b/apps/web/src/app-shell.integration.test.tsx @@ -0,0 +1,127 @@ +// @vitest-environment jsdom +import { cleanup, render, screen, within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import { AppShell, type InstanceContext } from './app-shell.js'; +import { type FleetDataSource, type FleetSnapshot } from './fleet/fleet-page.js'; + +afterEach(cleanup); + +const snapshot: FleetSnapshot = { + instances: [ + { + id: 'bravo', + name: 'Bravo', + url: 'http://user:secret@bravo.example:8080/private', + tags: ['west'], + }, + { id: 'alpha', name: 'Alpha', url: 'https://alpha.example/admin' }, + ], + statuses: new Map([ + ['bravo', { reachable: true, authenticated: false, latencyMs: 40 }], + ['alpha', { reachable: true, authenticated: true, latencyMs: 10 }], + ]), +}; + +function source(load: FleetDataSource['load']): FleetDataSource { + return { load }; +} + +describe('React AppShell and Fleet vertical slice', () => { + it('loads real injected data and renders canonical origins and owner routes', async () => { + let resolve!: (value: FleetSnapshot) => void; + const dataSource = source(() => new Promise((done) => (resolve = done))); + render(); + + expect(screen.getByRole('status', { name: 'Fleet loading status' }).textContent).toContain( + 'Loading instances', + ); + resolve(snapshot); + + const alpha = await screen.findByRole('row', { name: /Alpha/ }); + expect(within(alpha).getByRole('link', { name: 'Alpha' }).getAttribute('href')).toBe( + '/instances/alpha/overview', + ); + expect( + within(screen.getByRole('row', { name: /Bravo/ })) + .getByRole('link', { name: 'Open Bravo origin' }) + .getAttribute('href'), + ).toBe('http://bravo.example:8080'); + expect(screen.getByText('Version 0.1.0')).toBeTruthy(); + }); + + it('supports accessible search, status filtering, sorting, and visible selection', async () => { + const user = userEvent.setup(); + render( snapshot)} />); + await screen.findByRole('row', { name: /Alpha/ }); + + await user.click(screen.getByRole('button', { name: /Sort by latency/i })); + let rows = screen.getAllByRole('row').slice(1); + expect(rows[0]?.textContent).toContain('Alpha'); + await user.click(screen.getByRole('button', { name: /Sort by latency/i })); + rows = screen.getAllByRole('row').slice(1); + expect(rows[0]?.textContent).toContain('Bravo'); + + await user.selectOptions(screen.getByRole('combobox', { name: 'Status' }), 'auth'); + expect(screen.queryByRole('row', { name: /Alpha/ })).toBeNull(); + await user.click(screen.getByRole('checkbox', { name: 'Select all visible instances' })); + expect( + (screen.getByRole('checkbox', { name: 'Select Bravo' }) as HTMLInputElement).checked, + ).toBe(true); + expect(screen.getByText('1 selected')).toBeTruthy(); + + await user.type(screen.getByRole('searchbox', { name: 'Search instances' }), 'missing'); + expect(screen.getByText(/No instances match your search/i)).toBeTruthy(); + }); + + it('exposes error, retry, configured-empty, and unsafe-origin states without claiming a backend', async () => { + const user = userEvent.setup(); + const load = vi + .fn() + .mockRejectedValueOnce(new Error('collector unavailable')) + .mockResolvedValueOnce({ + instances: [{ id: 'unsafe', name: 'Unsafe', url: 'javascript:alert(1)' }], + statuses: new Map(), + }); + const first = render(); + + expect((await screen.findByRole('alert')).textContent).toContain('collector unavailable'); + await user.click(screen.getByRole('button', { name: 'Retry loading instances' })); + expect(await screen.findByRole('row', { name: /Unsafe/ })).toBeTruthy(); + expect(screen.queryByRole('link', { name: 'Open Unsafe origin' })).toBeNull(); + first.unmount(); + + const { unmount } = render( + ({ instances: [], statuses: new Map() }))} + />, + ); + expect(await screen.findByText(/No instances are configured/i)).toBeTruthy(); + unmount(); + }); + + it('binds instance context to the route owner and never leaks mismatched context', () => { + const instance: InstanceContext = { + id: 'owner', + name: 'Owner modem', + origin: 'https://owner.example/path', + status: 'online', + authentication: 'authenticated', + freshness: 'fresh', + }; + const { rerender } = render( + , + ); + expect(screen.getByRole('heading', { name: 'Messages' })).toBeTruthy(); + expect(screen.getByText('Owner modem')).toBeTruthy(); + expect(screen.getByRole('link', { name: 'Open original site' }).getAttribute('href')).toBe( + 'https://owner.example', + ); + + rerender(); + expect(screen.queryByText('Owner modem')).toBeNull(); + expect(screen.getByText(/Instance context is unavailable/i)).toBeTruthy(); + }); +}); diff --git a/apps/web/src/app-shell.tsx b/apps/web/src/app-shell.tsx new file mode 100644 index 0000000..255496e --- /dev/null +++ b/apps/web/src/app-shell.tsx @@ -0,0 +1,290 @@ +import type { ReactNode } from 'react'; + +import { + FleetPage, + canonicalHttpOrigin, + type FleetDataSource, + type FleetSnapshot, +} from './fleet/fleet-page.js'; +import { createFleetApiDataSource } from './fleet/fleet-api-data-source.js'; + +export type GlobalSection = 'fleet' | 'jobs' | 'audit' | 'settings'; +export type InstanceModule = + | 'overview' + | 'cellular' + | 'device-network' + | 'messages' + | 'calls' + | 'esim' + | 'notifications' + | 'automation' + | 'ota'; +export type RouteKind = + | 'redirect' + | 'fleet' + | 'instance-new' + | `instance-${InstanceModule}` + | 'jobs' + | 'job-detail' + | 'audit' + | 'audit-detail' + | 'settings-instances' + | 'settings-instance-detail' + | 'settings-system' + | 'not-found'; +export interface ResolvedRoute { + kind: RouteKind; + pathname: string; + params?: Readonly>; + to?: string; +} +export interface InstanceContext { + id: string; + name: string; + origin: string; + status: 'online' | 'offline' | 'auth-required' | 'degraded' | 'unknown'; + authentication: 'authenticated' | 'auth-required' | 'unknown'; + freshness: 'fresh' | 'stale' | 'expired' | 'unknown'; +} +export interface AppShellProps { + pathname: string; + version?: string; + instance?: InstanceContext; + fleetDataSource?: FleetDataSource; + fleetData?: FleetSnapshot; +} + +const MODULE_LABELS: Readonly> = { + overview: 'Overview', + cellular: 'Cellular', + 'device-network': 'Device Network', + messages: 'Messages', + calls: 'Calls', + esim: 'eSIM', + notifications: 'Notifications', + automation: 'Automation', + ota: 'OTA', +}; +const INSTANCE_MODULES = new Set(Object.keys(MODULE_LABELS)); + +function normalize(pathname: string): string { + const path = pathname.split(/[?#]/u, 1)[0] ?? '/'; + return path === '/' ? path : path.replace(/\/+$/u, '') || '/'; +} +function decode(value: string): string { + try { + return decodeURIComponent(value); + } catch { + return value; + } +} + +export function resolveRoute(input: string): ResolvedRoute { + const pathname = normalize(input); + if (pathname === '/') return { kind: 'redirect', pathname, to: '/fleet' }; + const staticRoutes: Readonly> = { + '/fleet': 'fleet', + '/instances/new': 'instance-new', + '/jobs': 'jobs', + '/audit': 'audit', + '/settings/instances': 'settings-instances', + '/settings/system': 'settings-system', + }; + if (staticRoutes[pathname]) return { kind: staticRoutes[pathname], pathname }; + const parts = pathname.split('/').filter(Boolean); + if (parts[0] === 'instances' && parts.length === 3 && INSTANCE_MODULES.has(parts[2] ?? '')) + return { + kind: `instance-${parts[2] as InstanceModule}`, + pathname, + params: { instanceId: decode(parts[1] ?? '') }, + }; + if (parts[0] === 'jobs' && parts.length === 2) + return { kind: 'job-detail', pathname, params: { jobId: decode(parts[1] ?? '') } }; + if (parts[0] === 'audit' && parts.length === 2) + return { kind: 'audit-detail', pathname, params: { eventId: decode(parts[1] ?? '') } }; + if (parts[0] === 'settings' && parts[1] === 'instances' && parts.length === 3) + return { + kind: 'settings-instance-detail', + pathname, + params: { instanceId: decode(parts[2] ?? '') }, + }; + return { kind: 'not-found', pathname }; +} + +function section(route: ResolvedRoute): GlobalSection | undefined { + if (route.kind === 'fleet' || route.kind === 'instance-new' || route.kind.startsWith('instance-')) + return 'fleet'; + if (route.kind.startsWith('job')) return 'jobs'; + if (route.kind.startsWith('audit')) return 'audit'; + if (route.kind.startsWith('settings')) return 'settings'; + return undefined; +} + +function InstanceSidebar({ + instance, + module, +}: { + instance: InstanceContext; + module: InstanceModule; +}) { + const origin = canonicalHttpOrigin(instance.origin); + return ( + + ); +} + +function Page({ + route, + fleetDataSource, + fleetData, + instance, +}: { + route: ResolvedRoute; + fleetDataSource: FleetDataSource | undefined; + fleetData: FleetSnapshot | undefined; + instance: InstanceContext | undefined; +}): ReactNode { + if (route.kind === 'fleet') + return ( + + ); + if (route.kind === 'not-found') + return ( +
+

Page not found

+

The requested console page does not exist.

+ Return to Fleet +
+ ); + if (route.kind.startsWith('instance-') && route.kind !== 'instance-new') { + const module = route.kind.slice('instance-'.length) as InstanceModule; + const ownsRoute = instance?.id === route.params?.instanceId; + return ( + <> +

{MODULE_LABELS[module]}

+ {ownsRoute ? ( +

Inspect {MODULE_LABELS[module].toLowerCase()} data and available operations.

+ ) : ( +

Instance context is unavailable for this route.

+ )} + + ); + } + const labels: Partial> = { + 'instance-new': 'Add instance', + jobs: 'Jobs', + 'job-detail': 'Job details', + audit: 'Audit', + 'audit-detail': 'Audit event', + 'settings-instances': 'Instance settings', + 'settings-instance-detail': 'Instance settings', + 'settings-system': 'System settings', + }; + return ( +
+

{labels[route.kind] ?? 'Multi SimAdmin'}

+

This route is ready for structured data and actions.

+
+ ); +} + +export function AppShell({ + pathname, + version = 'dev', + instance, + fleetDataSource, + fleetData, +}: AppShellProps) { + const resolved = resolveRoute(pathname); + const route = resolved.kind === 'redirect' ? resolveRoute(resolved.to ?? '/fleet') : resolved; + const currentSection = section(route); + const instanceModule = + route.kind.startsWith('instance-') && route.kind !== 'instance-new' + ? (route.kind.slice('instance-'.length) as InstanceModule) + : null; + const owner = instanceModule && instance?.id === route.params?.instanceId ? instance : undefined; + return ( +
+ + Skip to main content + +
+ + Multi SimAdmin + + Console + Version {version} +
+
+ + {owner && instanceModule ? ( + + ) : null} +
+ +
+
+
+ ); +} diff --git a/apps/web/src/fleet/fleet-api-data-source.ts b/apps/web/src/fleet/fleet-api-data-source.ts new file mode 100644 index 0000000..63efe83 --- /dev/null +++ b/apps/web/src/fleet/fleet-api-data-source.ts @@ -0,0 +1,22 @@ +import type { FleetDataSource, FleetSnapshot } from './fleet-page.js'; +import type { FleetInstance } from './fleet-table-view-model.js'; + +interface InstancePage { + readonly data?: readonly FleetInstance[]; +} + +export function createFleetApiDataSource(fetcher: typeof fetch = fetch): FleetDataSource { + return { + async load(): Promise { + const response = await fetcher('/api/v1/instances', { + method: 'GET', + credentials: 'same-origin', + headers: { accept: 'application/json' }, + }); + if (!response.ok) throw new Error(`Fleet request failed (${response.status}).`); + const body = (await response.json()) as InstancePage; + if (!Array.isArray(body.data)) throw new Error('Fleet response is invalid.'); + return { instances: body.data, statuses: new Map() }; + }, + }; +} diff --git a/apps/web/src/fleet/fleet-page.tsx b/apps/web/src/fleet/fleet-page.tsx new file mode 100644 index 0000000..ff1f585 --- /dev/null +++ b/apps/web/src/fleet/fleet-page.tsx @@ -0,0 +1,274 @@ +import { useEffect, useMemo, useRef, useState } from 'react'; + +import { + buildFleetTableViewModel, + type FleetFilter, + type FleetInstance, + type FleetSortColumn, + type FleetStatus, + type SortDirection, +} from './fleet-table-view-model.js'; + +export interface FleetSnapshot { + readonly instances: readonly FleetInstance[]; + readonly statuses: ReadonlyMap; +} + +export interface FleetDataSource { + load(): Promise; +} + +export interface FleetPageProps { + readonly dataSource?: FleetDataSource; + readonly initialData?: FleetSnapshot; +} + +const EMPTY_SNAPSHOT: FleetSnapshot = { instances: [], statuses: new Map() }; +const STATUS_LABELS: Readonly> = { + online: 'Online', + auth: 'Authentication required', + offline: 'Offline', + unknown: 'Unknown', +}; + +export function canonicalHttpOrigin(value: string): string | null { + try { + const url = new URL(value); + if (url.protocol !== 'http:' && url.protocol !== 'https:') return null; + url.username = ''; + url.password = ''; + return url.origin; + } catch { + return null; + } +} + +export function FleetPage({ dataSource, initialData }: FleetPageProps) { + const [snapshot, setSnapshot] = useState(initialData ?? null); + const [error, setError] = useState(null); + const [attempt, setAttempt] = useState(0); + const [query, setQuery] = useState(''); + const [filter, setFilter] = useState('all'); + const [sort, setSort] = useState<{ column: FleetSortColumn; direction: SortDirection }>({ + column: 'name', + direction: 'asc', + }); + const [selectedIds, setSelectedIds] = useState>(new Set()); + const selectAllRef = useRef(null); + + useEffect(() => { + if (initialData) { + setSnapshot(initialData); + setError(null); + return; + } + if (!dataSource) { + setSnapshot(EMPTY_SNAPSHOT); + return; + } + let active = true; + setSnapshot(null); + setError(null); + void dataSource.load().then( + (data) => { + if (active) setSnapshot(data); + }, + (reason: unknown) => { + if (active) + setError(reason instanceof Error ? reason.message : 'Unable to load instances.'); + }, + ); + return () => { + active = false; + }; + }, [attempt, dataSource, initialData]); + + const model = useMemo( + () => + buildFleetTableViewModel(snapshot?.instances ?? [], snapshot?.statuses ?? new Map(), { + query, + filter, + sort, + selectedIds, + }), + [filter, query, selectedIds, snapshot, sort], + ); + + useEffect(() => { + if (selectAllRef.current) + selectAllRef.current.indeterminate = model.visibleSelection.indeterminate; + }, [model.visibleSelection.indeterminate]); + + function changeSort(column: FleetSortColumn): void { + setSort((current) => ({ + column, + direction: current.column === column && current.direction === 'asc' ? 'desc' : 'asc', + })); + } + + function toggleOne(id: string, checked: boolean): void { + setSelectedIds((current) => { + const next = new Set(current); + if (checked) next.add(id); + else next.delete(id); + return next; + }); + } + + function toggleVisible(checked: boolean): void { + setSelectedIds((current) => { + const next = new Set(current); + for (const row of model.rows) { + if (checked) next.add(row.id); + else next.delete(row.id); + } + return next; + }); + } + + return ( +
+
+
+

Fleet

+

Find, compare, and manage SimAdmin instances.

+
+ + {model.selectedIds.length} selected + +
+
+ + +
+ + {!snapshot && !error ? ( +

+ Loading instances… +

+ ) : null} + {error ? ( +
+

Could not load instances: {error}

+ +
+ ) : null} + {snapshot && model.emptyReason ? ( +
+ {model.emptyReason === 'config' ?

No instances are configured.

: null} + {model.emptyReason === 'search' ? ( +

No instances match your search and filters.

+ ) : null} + {model.emptyReason === 'filter' ?

No instances match this status filter.

: null} +
+ ) : null} + {snapshot && model.rows.length > 0 ? ( +
+ + + + + + {(['name', 'status', 'latency'] as const).map((column) => { + const label = column[0]!.toUpperCase() + column.slice(1); + const active = sort.column === column; + return ( + + ); + })} + + + + + {model.rows.map((row) => { + const origin = canonicalHttpOrigin(row.instance.url); + return ( + + + + + + + + ); + })} + +
Fleet instances
+ toggleVisible(event.currentTarget.checked)} + /> + + + Origin
+ toggleOne(row.id, event.currentTarget.checked)} + /> + + + {row.displayName} + + {row.id} + + + {STATUS_LABELS[row.statusKind]} + + {row.latencyMs === undefined ? '—' : `${row.latencyMs} ms`} + {origin ? ( + + {origin} + + ) : ( + Invalid origin + )} +
+
+ ) : null} +
+ ); +} diff --git a/apps/web/src/fleet/fleet-table-view-model.test.ts b/apps/web/src/fleet/fleet-table-view-model.test.ts new file mode 100644 index 0000000..375f149 --- /dev/null +++ b/apps/web/src/fleet/fleet-table-view-model.test.ts @@ -0,0 +1,126 @@ +import { describe, expect, it } from 'vitest'; + +import { + buildFleetTableViewModel, + fleetStatusKind, + type FleetInstance, + type FleetStatus, +} from './fleet-table-view-model.js'; + +const instances: readonly FleetInstance[] = [ + { + id: 'charlie', + name: 'Charlie', + url: 'https://charlie.example', + description: 'Warehouse router', + tags: ['west'], + }, + { id: 'alpha', name: 'Alpha', url: 'https://alpha.example', tags: ['east'] }, + { id: 'bravo', name: 'Bravo', url: 'https://bravo.example' }, + { id: 'delta', name: '', url: 'https://delta.example' }, +]; + +const statuses: ReadonlyMap = new Map([ + [ + 'charlie', + { reachable: true, authenticated: true, latencyMs: 55, summary: { carrier: 'Acme' } }, + ], + ['alpha', { reachable: false, latencyMs: 8 }], + ['bravo', { reachable: true, authenticated: false, latencyMs: 20 }], +]); + +describe('fleet table view model', () => { + it('classifies missing, unreachable, authentication-required, and online status', () => { + expect(fleetStatusKind(undefined)).toBe('unknown'); + expect(fleetStatusKind({ reachable: false })).toBe('offline'); + expect(fleetStatusKind({ reachable: true, authenticated: false })).toBe('auth'); + expect(fleetStatusKind({ reachable: true, authenticated: true })).toBe('online'); + }); + + it('builds stable rows and defaults to ascending display-name order', () => { + const model = buildFleetTableViewModel(instances, statuses); + + expect(model.rows.map((row) => [row.id, row.displayName, row.statusKind])).toEqual([ + ['alpha', 'Alpha', 'offline'], + ['bravo', 'Bravo', 'auth'], + ['charlie', 'Charlie', 'online'], + ['delta', 'delta', 'unknown'], + ]); + expect(model.totalCount).toBe(4); + expect(model.visibleCount).toBe(4); + expect(model.emptyReason).toBeNull(); + }); + + it('searches user-visible instance and status fields case-insensitively', () => { + expect( + buildFleetTableViewModel(instances, statuses, { query: ' ACME ' }).rows.map((row) => row.id), + ).toEqual(['charlie']); + expect( + buildFleetTableViewModel(instances, statuses, { query: 'WEST' }).rows.map((row) => row.id), + ).toEqual(['charlie']); + }); + + it('filters by status and reports a useful empty reason', () => { + const filtered = buildFleetTableViewModel(instances, statuses, { filter: 'online' }); + expect(filtered.rows.map((row) => row.id)).toEqual(['charlie']); + + expect(buildFleetTableViewModel([], new Map()).emptyReason).toBe('config'); + expect( + buildFleetTableViewModel(instances, statuses, { query: 'not present' }).emptyReason, + ).toBe('search'); + expect( + buildFleetTableViewModel(instances, statuses, { filter: 'online', query: 'Alpha' }) + .emptyReason, + ).toBe('search'); + expect( + buildFleetTableViewModel([instances[1]!], statuses, { filter: 'online' }).emptyReason, + ).toBe('filter'); + expect( + buildFleetTableViewModel(instances, statuses, { filter: 'online', query: ' ' }).emptyReason, + ).toBeNull(); + expect( + buildFleetTableViewModel(instances, statuses, { filter: 'offline', query: 'Bravo' }) + .emptyReason, + ).toBe('search'); + }); + + it('sorts latency with missing latency last and status by operational priority', () => { + expect( + buildFleetTableViewModel(instances, statuses, { + sort: { column: 'latency', direction: 'asc' }, + }).rows.map((row) => row.id), + ).toEqual(['alpha', 'bravo', 'charlie', 'delta']); + expect( + buildFleetTableViewModel(instances, statuses, { + sort: { column: 'latency', direction: 'desc' }, + }).rows.map((row) => row.id), + ).toEqual(['charlie', 'bravo', 'alpha', 'delta']); + expect( + buildFleetTableViewModel(instances, statuses, { + sort: { column: 'status', direction: 'asc' }, + }).rows.map((row) => row.id), + ).toEqual(['charlie', 'bravo', 'alpha', 'delta']); + }); + + it('normalizes selection to known instances and exposes visible selection state', () => { + const model = buildFleetTableViewModel(instances, statuses, { + filter: 'online', + selectedIds: new Set(['charlie', 'alpha', 'missing']), + }); + + expect(model.rows[0]?.selected).toBe(true); + expect(model.selectedIds).toEqual(['alpha', 'charlie']); + expect(model.visibleSelection).toEqual({ + checked: true, + indeterminate: false, + selectedCount: 1, + rowCount: 1, + }); + }); + + it('does not mutate input arrays while sorting', () => { + const originalOrder = instances.map((instance) => instance.id); + buildFleetTableViewModel(instances, statuses, { sort: { column: 'name', direction: 'desc' } }); + expect(instances.map((instance) => instance.id)).toEqual(originalOrder); + }); +}); diff --git a/apps/web/src/fleet/fleet-table-view-model.ts b/apps/web/src/fleet/fleet-table-view-model.ts new file mode 100644 index 0000000..3e3114a --- /dev/null +++ b/apps/web/src/fleet/fleet-table-view-model.ts @@ -0,0 +1,182 @@ +export type FleetStatusKind = 'online' | 'auth' | 'offline' | 'unknown'; +export type FleetFilter = 'all' | FleetStatusKind; +export type FleetSortColumn = 'name' | 'status' | 'latency'; +export type SortDirection = 'asc' | 'desc'; +export type EmptyFleetReason = 'config' | 'search' | 'filter'; + +export interface FleetInstance { + readonly id: string; + readonly name?: string; + readonly url: string; + readonly description?: string; + readonly tags?: readonly string[]; +} + +export interface FleetStatus { + readonly reachable: boolean; + readonly authenticated?: boolean; + readonly latencyMs?: number; + readonly summary?: Readonly>; +} + +export interface FleetTableOptions { + readonly query?: string; + readonly filter?: FleetFilter; + readonly sort?: Readonly<{ + column: FleetSortColumn; + direction: SortDirection; + }>; + readonly selectedIds?: ReadonlySet; +} + +export interface FleetTableRow { + readonly id: string; + readonly displayName: string; + readonly instance: FleetInstance; + readonly status?: FleetStatus; + readonly statusKind: FleetStatusKind; + readonly latencyMs?: number; + readonly selected: boolean; +} + +export interface VisibleSelection { + readonly checked: boolean; + readonly indeterminate: boolean; + readonly selectedCount: number; + readonly rowCount: number; +} + +export interface FleetTableViewModel { + readonly rows: readonly FleetTableRow[]; + readonly totalCount: number; + readonly visibleCount: number; + readonly selectedIds: readonly string[]; + readonly visibleSelection: VisibleSelection; + readonly emptyReason: EmptyFleetReason | null; +} + +const STATUS_RANK: Readonly> = { + online: 0, + auth: 1, + offline: 2, + unknown: 3, +}; + +export function fleetStatusKind(status: FleetStatus | undefined): FleetStatusKind { + if (status === undefined) return 'unknown'; + if (!status.reachable) return 'offline'; + if (status.authenticated === false) return 'auth'; + return 'online'; +} + +function displayName(instance: FleetInstance): string { + const name = instance.name?.trim(); + return name ? name : instance.id; +} + +function searchableText(instance: FleetInstance, status: FleetStatus | undefined): string { + return [ + instance.id, + instance.name ?? '', + instance.url, + instance.description ?? '', + ...(instance.tags ?? []), + status?.summary === undefined ? '' : JSON.stringify(status.summary), + ] + .join(' ') + .toLocaleLowerCase(); +} + +function compareNames(left: FleetInstance, right: FleetInstance): number { + return displayName(left).localeCompare(displayName(right), undefined, { sensitivity: 'base' }); +} + +function compareInstances( + left: FleetInstance, + right: FleetInstance, + statuses: ReadonlyMap, + column: FleetSortColumn, + direction: SortDirection, +): number { + const leftStatus = statuses.get(left.id); + const rightStatus = statuses.get(right.id); + let comparison: number; + + if (column === 'status') { + comparison = + STATUS_RANK[fleetStatusKind(leftStatus)] - STATUS_RANK[fleetStatusKind(rightStatus)]; + } else if (column === 'latency') { + const leftMissing = leftStatus?.latencyMs === undefined; + const rightMissing = rightStatus?.latencyMs === undefined; + if (leftMissing !== rightMissing) return leftMissing ? 1 : -1; + comparison = (leftStatus?.latencyMs ?? 0) - (rightStatus?.latencyMs ?? 0); + } else { + comparison = compareNames(left, right); + } + + if (comparison !== 0) return direction === 'asc' ? comparison : -comparison; + return compareNames(left, right) || left.id.localeCompare(right.id); +} + +export function buildFleetTableViewModel( + instances: readonly FleetInstance[], + statuses: ReadonlyMap, + options: FleetTableOptions = {}, +): FleetTableViewModel { + const query = options.query?.trim().toLocaleLowerCase() ?? ''; + const filter = options.filter ?? 'all'; + const sort = options.sort ?? { column: 'name', direction: 'asc' }; + const knownIds = new Set(instances.map((instance) => instance.id)); + const selectedIds = [...(options.selectedIds ?? [])] + .filter((id) => knownIds.has(id)) + .sort((left, right) => left.localeCompare(right)); + const selectedIdSet = new Set(selectedIds); + + const visibleInstances = instances + .filter((instance) => { + const status = statuses.get(instance.id); + if (filter !== 'all' && fleetStatusKind(status) !== filter) return false; + return !query || searchableText(instance, status).includes(query); + }) + .slice() + .sort((left, right) => compareInstances(left, right, statuses, sort.column, sort.direction)); + + const rows: FleetTableRow[] = visibleInstances.map((instance) => { + const status = statuses.get(instance.id); + return { + id: instance.id, + displayName: displayName(instance), + instance, + ...(status === undefined ? {} : { status }), + statusKind: fleetStatusKind(status), + ...(status?.latencyMs === undefined ? {} : { latencyMs: status.latencyMs }), + selected: selectedIdSet.has(instance.id), + }; + }); + const visibleSelectedCount = rows.reduce((count, row) => count + (row.selected ? 1 : 0), 0); + const rowCount = rows.length; + const emptyReason: EmptyFleetReason | null = + instances.length === 0 + ? 'config' + : rowCount > 0 + ? null + : query + ? 'search' + : filter !== 'all' + ? 'filter' + : null; + + return { + rows, + totalCount: instances.length, + visibleCount: rowCount, + selectedIds, + visibleSelection: { + checked: rowCount > 0 && visibleSelectedCount === rowCount, + indeterminate: visibleSelectedCount > 0 && visibleSelectedCount < rowCount, + selectedCount: visibleSelectedCount, + rowCount, + }, + emptyReason, + }; +} diff --git a/apps/web/src/index.ts b/apps/web/src/index.ts index 330dd1d..38a09d2 100644 --- a/apps/web/src/index.ts +++ b/apps/web/src/index.ts @@ -1 +1,19 @@ +export { + AppShell, + resolveRoute, + type AppShellProps, + type InstanceContext, + type InstanceModule, + type ResolvedRoute, + type RouteKind, +} from './app-shell.js'; +export { + FleetPage, + canonicalHttpOrigin, + type FleetDataSource, + type FleetPageProps, + type FleetSnapshot, +} from './fleet/fleet-page.js'; +export * from './fleet/fleet-table-view-model.js'; + export const webWorkspaceReady = true; diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx new file mode 100644 index 0000000..07364e1 --- /dev/null +++ b/apps/web/src/main.tsx @@ -0,0 +1,14 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; + +import { AppShell } from './app-shell.js'; +import './styles.css'; + +const root = document.querySelector('#root'); +if (!root) throw new Error('Missing application root'); + +createRoot(root).render( + + + , +); diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css new file mode 100644 index 0000000..f7527dd --- /dev/null +++ b/apps/web/src/styles.css @@ -0,0 +1,263 @@ +:root { + font-family: Inter, ui-sans-serif, system-ui, sans-serif; + color: #e8edf5; + background: #0b1018; + --surface: #121a26; + --surface-raised: #192434; + --border: #2b394d; + --muted: #a9b6c8; + --accent: #75a7ff; + --danger: #ff8b91; + --radius: 0.5rem; + --space: clamp(0.75rem, 2vw, 1.5rem); +} +* { + box-sizing: border-box; +} +body { + margin: 0; + min-width: 20rem; + background: #0b1018; +} +a { + color: var(--accent); +} +button, +input, +select { + font: inherit; +} +button, +input, +select { + color: inherit; + background: var(--surface-raised); + border: 1px solid var(--border); + border-radius: 0.35rem; + padding: 0.5rem 0.65rem; +} +button, +input, +select, +a { + outline-offset: 3px; +} +.skip-link { + position: fixed; + top: -5rem; + left: 1rem; + z-index: 10; + background: white; + color: black; + padding: 0.75rem; +} +.skip-link:focus { + top: 1rem; +} +.app-topbar { + min-height: 3.5rem; + padding: 0 var(--space); + border-bottom: 1px solid var(--border); + display: flex; + align-items: center; + gap: 1rem; +} +.product-name { + margin-right: auto; + font-weight: 750; + color: inherit; + text-decoration: none; +} +.connection-status { + color: var(--muted); +} +.app-layout { + display: grid; + grid-template-columns: 11rem minmax(0, 1fr); + min-height: calc(100vh - 3.5rem); +} +.global-navigation, +.instance-context { + padding: var(--space); + border-right: 1px solid var(--border); +} +.global-navigation ul, +.instance-context ul { + list-style: none; + padding: 0; + margin: 0; + display: grid; + gap: 0.35rem; +} +.global-navigation a, +.instance-context nav a { + display: block; + padding: 0.55rem; + border-radius: 0.35rem; + text-decoration: none; +} +a[aria-current='page'] { + background: var(--surface-raised); + color: white; +} +.instance-context { + grid-column: 2; + grid-row: 1; + border-bottom: 1px solid var(--border); +} +.instance-context + main { + grid-column: 2; + grid-row: 2; +} +.instance-context code { + display: block; + color: var(--muted); +} +.instance-context dl div { + display: flex; + justify-content: space-between; + gap: 1rem; +} +main { + min-width: 0; + padding: var(--space); +} +.fleet-heading, +.fleet-toolbar { + display: flex; + align-items: end; + justify-content: space-between; + gap: 1rem; + flex-wrap: wrap; +} +.fleet-toolbar { + justify-content: flex-start; + padding: 1rem 0; +} +.fleet-toolbar label { + display: grid; + gap: 0.3rem; + min-width: min(100%, 14rem); +} +.fleet-toolbar label:first-child { + flex: 1; +} +.selection-summary, +small { + color: var(--muted); +} +.table-scroll { + overflow-x: auto; + border: 1px solid var(--border); + border-radius: var(--radius); +} +table { + width: 100%; + border-collapse: collapse; + white-space: nowrap; +} +caption { + text-align: left; + padding: 0.75rem; + font-weight: 700; +} +th, +td { + padding: 0.6rem 0.75rem; + border-top: 1px solid var(--border); + text-align: left; +} +tbody tr:hover, +tr[aria-selected='true'] { + background: var(--surface); +} +tbody th small { + display: block; + font-weight: normal; +} +.select-column { + width: 3rem; +} +.status { + display: inline-block; + padding: 0.2rem 0.45rem; + border-radius: 999px; + background: var(--surface-raised); +} +.status-online { + color: #75e6a3; +} +.status-offline, +.status-auth { + color: #ffbd76; +} +.state-panel { + padding: 1rem; + border: 1px dashed var(--border); + border-radius: var(--radius); +} +.state-error { + color: var(--danger); + border-color: currentColor; +} +@media (max-width: 48rem) { + .app-topbar { + flex-wrap: wrap; + padding-block: 0.6rem; + } + .app-layout { + display: block; + } + .global-navigation { + border-right: 0; + border-bottom: 1px solid var(--border); + overflow-x: auto; + } + .global-navigation ul { + display: flex; + } + .instance-context { + border-right: 0; + } + .fleet-toolbar label { + width: 100%; + } + .table-scroll { + overflow: visible; + border: 0; + } + .dense-table, + .dense-table tbody, + .dense-table tr, + .dense-table th, + .dense-table td { + display: block; + width: 100%; + white-space: normal; + } + .dense-table thead { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip: rect(0 0 0 0); + } + .dense-table tbody tr { + margin-block: 0.75rem; + padding: 0.75rem; + border: 1px solid var(--border); + border-radius: var(--radius); + } + .dense-table tbody td, + .dense-table tbody th { + border: 0; + padding: 0.3rem 0; + } +} +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + scroll-behavior: auto !important; + } +} diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 4491d1b..b6412b0 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -1,7 +1,11 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src" + "lib": ["ES2022", "DOM"], + "rootDir": "src", + "jsx": "react-jsx", + "types": ["vite/client"] }, - "include": ["src/**/*.ts"] + "include": ["src/**/*.ts", "src/**/*.tsx"], + "exclude": ["src/**/*.test.ts", "src/**/*.test.tsx"] } diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts new file mode 100644 index 0000000..fabde1a --- /dev/null +++ b/apps/web/vite.config.ts @@ -0,0 +1,6 @@ +import react from '@vitejs/plugin-react'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [react()], +}); diff --git a/eslint.config.js b/eslint.config.js index bcec9c8..07c2eca 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -19,7 +19,7 @@ export default defineConfig( }, }, { - files: ['**/*.ts'], + files: ['**/*.{ts,tsx}'], extends: [...tseslint.configs.recommended], languageOptions: { parser: tseslint.parser, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2412d0e..a6ed6c2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,7 +32,7 @@ importers: version: 8.53.0(eslint@9.39.2)(typescript@5.9.3) vitest: specifier: 4.1.0 - version: 4.1.0(@types/node@24.13.3)(vite@7.3.6(@types/node@24.13.3)) + version: 4.1.0(@types/node@24.13.3)(jsdom@28.1.0)(vite@7.3.6(@types/node@24.13.3)) apps/api: dependencies: @@ -59,7 +59,36 @@ importers: specifier: 24.13.3 version: 24.13.3 - apps/web: {} + apps/web: + dependencies: + react: + specifier: 19.2.4 + version: 19.2.4 + react-dom: + specifier: 19.2.4 + version: 19.2.4(react@19.2.4) + devDependencies: + '@testing-library/react': + specifier: 16.3.0 + version: 16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@testing-library/user-event': + specifier: 14.6.1 + version: 14.6.1(@testing-library/dom@10.4.1) + '@types/react': + specifier: 19.2.7 + version: 19.2.7 + '@types/react-dom': + specifier: 19.2.3 + version: 19.2.3(@types/react@19.2.7) + '@vitejs/plugin-react': + specifier: 5.1.1 + version: 5.1.1(vite@7.3.5(@types/node@24.13.3)) + jsdom: + specifier: 27.2.0 + version: 27.2.0 + vite: + specifier: 7.3.5 + version: 7.3.5(@types/node@24.13.3) packages/contracts: {} @@ -69,156 +98,459 @@ importers: packages: + '@acemir/cssom@0.9.31': + resolution: {integrity: sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==} + + '@asamuzakjp/css-color@4.1.2': + resolution: {integrity: sha512-NfBUvBaYgKIuq6E/RBLY1m0IohzNHAYyaJGuTK79Z23uNwmz2jl1mPsC5ZxCCxylinKhT1Amn5oNTlx1wN8cQg==} + + '@asamuzakjp/css-color@5.1.11': + resolution: {integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/dom-selector@6.8.1': + resolution: {integrity: sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==} + + '@asamuzakjp/generational-cache@1.0.1': + resolution: {integrity: sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/nwsapi@2.3.9': + resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.7': + resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.29.7': + resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.29.7': + resolution: {integrity: sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.29.7': + resolution: {integrity: sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.7': + resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + + '@bramus/specificity@2.4.2': + resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} + hasBin: true + + '@csstools/color-helpers@6.1.0': + resolution: {integrity: sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==} + engines: {node: '>=20.19.0'} + + '@csstools/css-calc@3.2.1': + resolution: {integrity: sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-color-parser@4.1.9': + resolution: {integrity: sha512-paQcIaOO53Rk5+YrBaBjm/SgrV4INImjo2BT1DtQRYr+XeTRbeAYlS+jxXp9drqvKmtFnWRJKIalDLhZZDu42A==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-parser-algorithms@4.0.0': + resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.6': + resolution: {integrity: sha512-TcJCWFbXLPpJYq6z7bfOyjWYJDiDg2/I4gyUC9pqPNqHFRIey0EB0q0L5cSnQDfWJg8Jd6VadakxdIez/3zkqQ==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + + '@csstools/css-tokenizer@4.0.0': + resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} + engines: {node: '>=20.19.0'} + + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.28.1': resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.28.1': resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} engines: {node: '>=18'} cpu: [arm64] os: [android] + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.28.1': resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} engines: {node: '>=18'} cpu: [arm] os: [android] + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.28.1': resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} engines: {node: '>=18'} cpu: [x64] os: [android] + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.28.1': resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.28.1': resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.28.1': resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.28.1': resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.28.1': resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.28.1': resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} engines: {node: '>=18'} cpu: [arm] os: [linux] + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.28.1': resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.28.1': resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.28.1': resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.28.1': resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.28.1': resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.28.1': resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.28.1': resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} engines: {node: '>=18'} cpu: [x64] os: [linux] + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-arm64@0.28.1': resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.28.1': resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-arm64@0.28.1': resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.28.1': resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/openharmony-arm64@0.28.1': resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.28.1': resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.28.1': resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.28.1': resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.28.1': resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} engines: {node: '>=18'} @@ -263,6 +595,15 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@exodus/bytes@1.15.1': + resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@noble/hashes': ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + '@noble/hashes': + optional: true + '@fastify/accept-negotiator@2.0.1': resolution: {integrity: sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==} @@ -310,9 +651,22 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@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==} + '@lukeed/ms@2.0.2': resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==} engines: {node: '>=8'} @@ -320,6 +674,9 @@ packages: '@pinojs/redact@0.4.0': resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + '@rolldown/pluginutils@1.0.0-beta.47': + resolution: {integrity: sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==} + '@rollup/rollup-android-arm-eabi@4.62.2': resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==} cpu: [arm] @@ -461,6 +818,46 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/react@16.3.0': + resolution: {integrity: sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@testing-library/user-event@14.6.1': + resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/better-sqlite3@7.6.13': resolution: {integrity: sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==} @@ -479,6 +876,14 @@ packages: '@types/node@24.13.3': resolution: {integrity: sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==} + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.7': + resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==} + '@typescript-eslint/eslint-plugin@8.53.0': resolution: {integrity: sha512-eEXsVvLPu8Z4PkFibtuFJLJOTAV/nPdgtSjkGoPpddpFk3/ym2oy97jynY6ic2m6+nc5M8SE1e9v/mHKsulcJg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -538,6 +943,12 @@ packages: resolution: {integrity: sha512-LZ2NqIHFhvFwxG0qZeLL9DvdNAHPGCY5dIRwBhyYeU+LfLhcStE1ImjsuTG/WaVh3XysGaeLW8Rqq7cGkPCFvw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@vitejs/plugin-react@5.1.1': + resolution: {integrity: sha512-WQfkSw0QbQ5aJ2CHYw23ZGkqnRwqKHD/KYsMeTkZzPT4Jcf0DcBxBtwMJxnu6E7oxw5+JC6ZAiePgh28uJ1HBA==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + '@vitest/expect@4.1.0': resolution: {integrity: sha512-EIxG7k4wlWweuCLG9Y5InKFwpMEOyrMb6ZJ1ihYu02LVj/bzUwn2VMU+13PinsjRW75XnITeFrQBMH5+dLvCDA==} @@ -580,6 +991,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + ajv-formats@3.0.1: resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} peerDependencies: @@ -594,13 +1009,24 @@ packages: ajv@8.20.0: resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -622,10 +1048,18 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + baseline-browser-mapping@2.10.43: + resolution: {integrity: sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==} + engines: {node: '>=6.0.0'} + hasBin: true + better-sqlite3@12.11.1: resolution: {integrity: sha512-dq9AtApgg5PGFtBzPFSBl3HZQjHok5gaQCM6zh2Yk0aSmDCs1CbnVI8/HgASQkNKsWFpseIO9beg5xxpYhbIfA==} engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x || 26.x} + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} @@ -642,6 +1076,11 @@ packages: resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==} engines: {node: 18 || 20 || >=22} + browserslist@4.28.6: + resolution: {integrity: sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} @@ -649,6 +1088,9 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + caniuse-lite@1.0.30001806: + resolution: {integrity: sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==} + chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} @@ -685,6 +1127,29 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + cssstyle@5.3.7: + resolution: {integrity: sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ==} + engines: {node: '>=20'} + + cssstyle@6.2.0: + resolution: {integrity: sha512-Fm5NvhYathRnXNVndkUsCCuR63DCLVVwGOOwQw782coXFi5HhkXdu289l59HlXZBawsyNccXfWRYvLzcDCdDig==} + engines: {node: '>=20'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + data-urls@6.0.1: + resolution: {integrity: sha512-euIQENZg6x8mj3fO6o9+fOW8MimUI4PpD/fZBhJfeioZVy9TUpM4UY7KjQNVZFlqwJ0UdzRDzkycB997HEq1BQ==} + engines: {node: '>=20'} + + data-urls@7.0.0: + resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -694,6 +1159,9 @@ packages: supports-color: optional: true + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} @@ -717,6 +1185,9 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + drizzle-orm@0.45.2: resolution: {integrity: sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q==} peerDependencies: @@ -809,17 +1280,33 @@ packages: sqlite3: optional: true + electron-to-chromium@1.5.392: + resolution: {integrity: sha512-1yQq3VQCZRwsnYc67Oc+1fge6Lwtn0hzi6zmEVkB61Zx21kTbwJAW4dFLadl5Rc1tKhG/kSpYXnfiAhu0f0a1g==} + end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + entities@8.0.0: + resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} + engines: {node: '>=20.19.0'} + es-module-lexer@2.3.1: resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + esbuild@0.28.1: resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} engines: {node: '>=18'} hasBin: true + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} @@ -952,6 +1439,10 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} @@ -971,10 +1462,30 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + + html-encoding-sniffer@6.0.0: + resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + http-errors@2.0.1: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -1012,13 +1523,42 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-yaml@4.3.0: resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==} hasBin: true + jsdom@27.2.0: + resolution: {integrity: sha512-454TI39PeRDW1LgpyLPyURtB4Zx1tklSr6+OFOipsxGUH1WMTvk6C65JQdrj455+DP2uJ1+veBEHTGFKWVLFoA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + + jsdom@28.1.0: + resolution: {integrity: sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -1034,6 +1574,11 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -1055,9 +1600,19 @@ packages: resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} engines: {node: 20 || >=22} + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + mime@3.0.0: resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} engines: {node: '>=10.0.0'} @@ -1106,6 +1661,10 @@ packages: resolution: {integrity: sha512-W5ZNO5KRPB5TkYmGVD9F6YqhsglXJzE6etpbmT+f6EQElhiX/UTG551cnsRGvLG3fyZEg9HwaDmNmj5nwJ4z9g==} engines: {node: '>=10'} + node-releases@2.0.51: + resolution: {integrity: sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==} + engines: {node: '>=18'} + obug@2.1.3: resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==} engines: {node: '>=12.20.0'} @@ -1133,6 +1692,9 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse5@8.0.1: + resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -1184,6 +1746,10 @@ packages: engines: {node: '>=14'} hasBin: true + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + process-warning@4.0.1: resolution: {integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==} @@ -1204,6 +1770,22 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true + react-dom@19.2.4: + resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} + peerDependencies: + react: ^19.2.4 + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + + react-refresh@0.18.0: + resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} + engines: {node: '>=0.10.0'} + + react@19.2.4: + resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} + engines: {node: '>=0.10.0'} + readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} @@ -1250,9 +1832,23 @@ packages: resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} engines: {node: '>=10'} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + secure-json-parse@4.1.0: resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + semver@7.8.5: resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} engines: {node: '>=10'} @@ -1317,6 +1913,9 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + tar-fs@2.1.5: resolution: {integrity: sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==} @@ -1343,6 +1942,13 @@ packages: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} + tldts-core@7.4.8: + resolution: {integrity: sha512-c1P7u0EhACHj7lPy4MJm8iTFEU8+nB0LCtddH0fhP7noaVoXAqafMtOOeX+ulpuPBqnrRgRhw494RICT3mbhnw==} + + tldts@7.4.8: + resolution: {integrity: sha512-htwgN/8KRB3z3vnC0BOETVh2m499g5GmyTK9Wq5JBLX3FNz6tSBveAd+fQhzy9hkjif8vy2jwDMR1sGhLtZl2A==} + hasBin: true + toad-cache@3.7.4: resolution: {integrity: sha512-m1TdR/rvT7kgGJZhspNtXdsdYk0fddFpJJFlG5s+UkPFo6lkLoZ3YLOaovPYjq1R75NP5JfeTlSHaOsE09peCg==} engines: {node: '>=20'} @@ -1351,6 +1957,14 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + tough-cookie@6.0.2: + resolution: {integrity: sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==} + engines: {node: '>=16'} + + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + ts-api-utils@2.5.0: resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} engines: {node: '>=18.12'} @@ -1379,12 +1993,62 @@ packages: undici-types@7.18.2: resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + undici@7.28.0: + resolution: {integrity: sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==} + engines: {node: '>=20.18.1'} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + vite@7.3.5: + resolution: {integrity: sha512-KuOaNhcnGFN2zIPGA7wRmzF+lJA1sea7rHq17aiJ++9lzY1WWG6Jpwqwe1KNbRVPIqHmr8GLYx7jbrQcN/7/ww==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + vite@7.3.6: resolution: {integrity: sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1460,6 +2124,35 @@ packages: jsdom: optional: true + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + + whatwg-url@15.1.0: + resolution: {integrity: sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==} + engines: {node: '>=20'} + + whatwg-url@16.0.1: + resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -1477,87 +2170,362 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@8.21.1: + resolution: {integrity: sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} snapshots: + '@acemir/cssom@0.9.31': {} + + '@asamuzakjp/css-color@4.1.2': + dependencies: + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.9(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + lru-cache: 11.5.2 + + '@asamuzakjp/css-color@5.1.11': + dependencies: + '@asamuzakjp/generational-cache': 1.0.1 + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.9(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + optional: true + + '@asamuzakjp/dom-selector@6.8.1': + dependencies: + '@asamuzakjp/nwsapi': 2.3.9 + bidi-js: 1.0.3 + css-tree: 3.2.1 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.5.2 + + '@asamuzakjp/generational-cache@1.0.1': + optional: true + + '@asamuzakjp/nwsapi@2.3.9': {} + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.7': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.6 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.29.7': {} + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/plugin-transform-react-jsx-self@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-react-jsx-source@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/runtime@7.29.7': {} + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/traverse@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@bramus/specificity@2.4.2': + dependencies: + css-tree: 3.2.1 + optional: true + + '@csstools/color-helpers@6.1.0': {} + + '@csstools/css-calc@3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-color-parser@4.1.9(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/color-helpers': 6.1.0 + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.6(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 + + '@csstools/css-tokenizer@4.0.0': {} + + '@esbuild/aix-ppc64@0.27.7': + optional: true + '@esbuild/aix-ppc64@0.28.1': optional: true + '@esbuild/android-arm64@0.27.7': + optional: true + '@esbuild/android-arm64@0.28.1': optional: true + '@esbuild/android-arm@0.27.7': + optional: true + '@esbuild/android-arm@0.28.1': optional: true + '@esbuild/android-x64@0.27.7': + optional: true + '@esbuild/android-x64@0.28.1': optional: true + '@esbuild/darwin-arm64@0.27.7': + optional: true + '@esbuild/darwin-arm64@0.28.1': optional: true + '@esbuild/darwin-x64@0.27.7': + optional: true + '@esbuild/darwin-x64@0.28.1': optional: true + '@esbuild/freebsd-arm64@0.27.7': + optional: true + '@esbuild/freebsd-arm64@0.28.1': optional: true + '@esbuild/freebsd-x64@0.27.7': + optional: true + '@esbuild/freebsd-x64@0.28.1': optional: true + '@esbuild/linux-arm64@0.27.7': + optional: true + '@esbuild/linux-arm64@0.28.1': optional: true + '@esbuild/linux-arm@0.27.7': + optional: true + '@esbuild/linux-arm@0.28.1': optional: true + '@esbuild/linux-ia32@0.27.7': + optional: true + '@esbuild/linux-ia32@0.28.1': optional: true + '@esbuild/linux-loong64@0.27.7': + optional: true + '@esbuild/linux-loong64@0.28.1': optional: true + '@esbuild/linux-mips64el@0.27.7': + optional: true + '@esbuild/linux-mips64el@0.28.1': optional: true + '@esbuild/linux-ppc64@0.27.7': + optional: true + '@esbuild/linux-ppc64@0.28.1': optional: true + '@esbuild/linux-riscv64@0.27.7': + optional: true + '@esbuild/linux-riscv64@0.28.1': optional: true + '@esbuild/linux-s390x@0.27.7': + optional: true + '@esbuild/linux-s390x@0.28.1': optional: true + '@esbuild/linux-x64@0.27.7': + optional: true + '@esbuild/linux-x64@0.28.1': optional: true + '@esbuild/netbsd-arm64@0.27.7': + optional: true + '@esbuild/netbsd-arm64@0.28.1': optional: true + '@esbuild/netbsd-x64@0.27.7': + optional: true + '@esbuild/netbsd-x64@0.28.1': optional: true + '@esbuild/openbsd-arm64@0.27.7': + optional: true + '@esbuild/openbsd-arm64@0.28.1': optional: true + '@esbuild/openbsd-x64@0.27.7': + optional: true + '@esbuild/openbsd-x64@0.28.1': optional: true + '@esbuild/openharmony-arm64@0.27.7': + optional: true + '@esbuild/openharmony-arm64@0.28.1': optional: true + '@esbuild/sunos-x64@0.27.7': + optional: true + '@esbuild/sunos-x64@0.28.1': optional: true + '@esbuild/win32-arm64@0.27.7': + optional: true + '@esbuild/win32-arm64@0.28.1': optional: true + '@esbuild/win32-ia32@0.27.7': + optional: true + '@esbuild/win32-ia32@0.28.1': optional: true + '@esbuild/win32-x64@0.27.7': + optional: true + '@esbuild/win32-x64@0.28.1': optional: true @@ -1607,6 +2575,9 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 + '@exodus/bytes@1.15.1': + optional: true + '@fastify/accept-negotiator@2.0.1': {} '@fastify/ajv-compiler@4.0.5': @@ -1665,12 +2636,31 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@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 + '@lukeed/ms@2.0.2': {} '@pinojs/redact@0.4.0': {} + '@rolldown/pluginutils@1.0.0-beta.47': {} + '@rollup/rollup-android-arm-eabi@4.62.2': optional: true @@ -1748,6 +2738,54 @@ snapshots: '@standard-schema/spec@1.1.0': {} + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/runtime': 7.29.7 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/react@16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@babel/runtime': 7.29.7 + '@testing-library/dom': 10.4.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': + dependencies: + '@testing-library/dom': 10.4.1 + + '@types/aria-query@5.0.4': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.7 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.7 + '@types/better-sqlite3@7.6.13': dependencies: '@types/node': 24.13.3 @@ -1767,6 +2805,14 @@ snapshots: dependencies: undici-types: 7.18.2 + '@types/react-dom@19.2.3(@types/react@19.2.7)': + dependencies: + '@types/react': 19.2.7 + + '@types/react@19.2.7': + dependencies: + csstype: 3.2.3 + '@typescript-eslint/eslint-plugin@8.53.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -1858,6 +2904,18 @@ snapshots: '@typescript-eslint/types': 8.53.0 eslint-visitor-keys: 4.2.1 + '@vitejs/plugin-react@5.1.1(vite@7.3.5(@types/node@24.13.3))': + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-transform-react-jsx-self': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx-source': 7.29.7(@babel/core@7.29.7) + '@rolldown/pluginutils': 1.0.0-beta.47 + '@types/babel__core': 7.20.5 + react-refresh: 0.18.0 + vite: 7.3.5(@types/node@24.13.3) + transitivePeerDependencies: + - supports-color + '@vitest/expect@4.1.0': dependencies: '@standard-schema/spec': 1.1.0 @@ -1907,6 +2965,8 @@ snapshots: acorn@8.17.0: {} + agent-base@7.1.4: {} + ajv-formats@3.0.1(ajv@8.20.0): optionalDependencies: ajv: 8.20.0 @@ -1925,12 +2985,20 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + ansi-regex@5.0.1: {} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 + ansi-styles@5.2.0: {} + argparse@2.0.1: {} + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + assertion-error@2.0.1: {} atomic-sleep@1.0.0: {} @@ -1946,11 +3014,17 @@ snapshots: base64-js@1.5.1: {} + baseline-browser-mapping@2.10.43: {} + better-sqlite3@12.11.1: dependencies: bindings: 1.5.0 prebuild-install: 7.1.3 + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + bindings@1.5.0: dependencies: file-uri-to-path: 1.0.0 @@ -1974,6 +3048,14 @@ snapshots: dependencies: balanced-match: 4.0.4 + browserslist@4.28.6: + dependencies: + baseline-browser-mapping: 2.10.43 + caniuse-lite: 1.0.30001806 + electron-to-chromium: 1.5.392 + node-releases: 2.0.51 + update-browserslist-db: 1.2.3(browserslist@4.28.6) + buffer@5.7.1: dependencies: base64-js: 1.5.1 @@ -1981,6 +3063,8 @@ snapshots: callsites@3.1.0: {} + caniuse-lite@1.0.30001806: {} + chai@6.2.2: {} chalk@4.1.2: @@ -2010,10 +3094,47 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + + cssstyle@5.3.7: + dependencies: + '@asamuzakjp/css-color': 4.1.2 + '@csstools/css-syntax-patches-for-csstree': 1.1.6(css-tree@3.2.1) + css-tree: 3.2.1 + lru-cache: 11.5.2 + + cssstyle@6.2.0: + dependencies: + '@asamuzakjp/css-color': 5.1.11 + '@csstools/css-syntax-patches-for-csstree': 1.1.6(css-tree@3.2.1) + css-tree: 3.2.1 + lru-cache: 11.5.2 + optional: true + + csstype@3.2.3: {} + + data-urls@6.0.1: + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 15.1.0 + + data-urls@7.0.0: + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + transitivePeerDependencies: + - '@noble/hashes' + optional: true + debug@4.4.3: dependencies: ms: 2.1.3 + decimal.js@10.6.0: {} + decompress-response@6.0.0: dependencies: mimic-response: 3.1.0 @@ -2028,17 +3149,52 @@ snapshots: detect-libc@2.1.2: {} + dom-accessibility-api@0.5.16: {} + drizzle-orm@0.45.2(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1): optionalDependencies: '@types/better-sqlite3': 7.6.13 better-sqlite3: 12.11.1 + electron-to-chromium@1.5.392: {} + end-of-stream@1.4.5: dependencies: once: 1.4.0 + entities@8.0.0: {} + es-module-lexer@2.3.1: {} + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + esbuild@0.28.1: optionalDependencies: '@esbuild/aix-ppc64': 0.28.1 @@ -2068,6 +3224,8 @@ snapshots: '@esbuild/win32-ia32': 0.28.1 '@esbuild/win32-x64': 0.28.1 + escalade@3.2.0: {} + escape-html@1.0.3: {} escape-string-regexp@4.0.0: {} @@ -2228,6 +3386,8 @@ snapshots: fsevents@2.3.3: optional: true + gensync@1.0.0-beta.2: {} + github-from-package@0.0.0: {} glob-parent@6.0.2: @@ -2244,6 +3404,17 @@ snapshots: has-flag@4.0.0: {} + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + + html-encoding-sniffer@6.0.0: + dependencies: + '@exodus/bytes': 1.15.1 + transitivePeerDependencies: + - '@noble/hashes' + optional: true + http-errors@2.0.1: dependencies: depd: 2.0.0 @@ -2252,6 +3423,24 @@ snapshots: statuses: 2.0.2 toidentifier: 1.0.1 + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + ieee754@1.2.1: {} ignore@5.3.2: {} @@ -2277,12 +3466,73 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-potential-custom-element-name@1.0.1: {} + isexe@2.0.0: {} + js-tokens@4.0.0: {} + js-yaml@4.3.0: dependencies: argparse: 2.0.1 + jsdom@27.2.0: + dependencies: + '@acemir/cssom': 0.9.31 + '@asamuzakjp/dom-selector': 6.8.1 + cssstyle: 5.3.7 + data-urls: 6.0.1 + decimal.js: 10.6.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + parse5: 8.0.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.2 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 15.1.0 + ws: 8.21.1 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + jsdom@28.1.0: + dependencies: + '@acemir/cssom': 0.9.31 + '@asamuzakjp/dom-selector': 6.8.1 + '@bramus/specificity': 2.4.2 + '@exodus/bytes': 1.15.1 + cssstyle: 6.2.0 + data-urls: 7.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 6.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + parse5: 8.0.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.2 + undici: 7.28.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@noble/hashes' + - supports-color + optional: true + + jsesc@3.1.0: {} + json-buffer@3.0.1: {} json-schema-ref-resolver@3.0.0: @@ -2295,6 +3545,8 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + json5@2.2.3: {} + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -2318,10 +3570,18 @@ snapshots: lru-cache@11.5.2: {} + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lz-string@1.5.0: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + mdn-data@2.27.1: {} + mime@3.0.0: {} mimic-response@3.1.0: {} @@ -2356,6 +3616,8 @@ snapshots: dependencies: semver: 7.8.5 + node-releases@2.0.51: {} + obug@2.1.3: {} on-exit-leak-free@2.1.2: {} @@ -2385,6 +3647,10 @@ snapshots: dependencies: callsites: 3.1.0 + parse5@8.0.1: + dependencies: + entities: 8.0.0 + path-exists@4.0.0: {} path-key@3.1.1: {} @@ -2445,6 +3711,12 @@ snapshots: prettier@3.8.1: {} + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + process-warning@4.0.1: {} process-warning@5.0.0: {} @@ -2465,6 +3737,17 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 + react-dom@19.2.4(react@19.2.4): + dependencies: + react: 19.2.4 + scheduler: 0.27.0 + + react-is@17.0.2: {} + + react-refresh@0.18.0: {} + + react@19.2.4: {} + readable-stream@3.6.2: dependencies: inherits: 2.0.4 @@ -2524,8 +3807,18 @@ snapshots: safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + scheduler@0.27.0: {} + secure-json-parse@4.1.0: {} + semver@6.3.1: {} + semver@7.8.5: {} set-cookie-parser@2.7.2: {} @@ -2574,6 +3867,8 @@ snapshots: dependencies: has-flag: 4.0.0 + symbol-tree@3.2.4: {} + tar-fs@2.1.5: dependencies: chownr: 1.1.4 @@ -2604,10 +3899,24 @@ snapshots: tinyrainbow@3.1.0: {} + tldts-core@7.4.8: {} + + tldts@7.4.8: + dependencies: + tldts-core: 7.4.8 + toad-cache@3.7.4: {} toidentifier@1.0.1: {} + tough-cookie@6.0.2: + dependencies: + tldts: 7.4.8 + + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + ts-api-utils@2.5.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -2635,12 +3944,33 @@ snapshots: undici-types@7.18.2: {} + undici@7.28.0: + optional: true + + update-browserslist-db@1.2.3(browserslist@4.28.6): + dependencies: + browserslist: 4.28.6 + escalade: 3.2.0 + picocolors: 1.1.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1 util-deprecate@1.0.2: {} + vite@7.3.5(@types/node@24.13.3): + dependencies: + esbuild: 0.27.7 + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 + postcss: 8.5.19 + rollup: 4.62.2 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 24.13.3 + fsevents: 2.3.3 + vite@7.3.6(@types/node@24.13.3): dependencies: esbuild: 0.28.1 @@ -2653,7 +3983,7 @@ snapshots: '@types/node': 24.13.3 fsevents: 2.3.3 - vitest@4.1.0(@types/node@24.13.3)(vite@7.3.6(@types/node@24.13.3)): + vitest@4.1.0(@types/node@24.13.3)(jsdom@28.1.0)(vite@7.3.6(@types/node@24.13.3)): dependencies: '@vitest/expect': 4.1.0 '@vitest/mocker': 4.1.0(vite@7.3.6(@types/node@24.13.3)) @@ -2677,9 +4007,38 @@ snapshots: why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.13.3 + jsdom: 28.1.0 transitivePeerDependencies: - msw + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@8.0.1: {} + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + + whatwg-mimetype@5.0.0: {} + + whatwg-url@15.1.0: + dependencies: + tr46: 6.0.0 + webidl-conversions: 8.0.1 + + whatwg-url@16.0.1: + dependencies: + '@exodus/bytes': 1.15.1 + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + optional: true + which@2.0.2: dependencies: isexe: 2.0.0 @@ -2693,4 +4052,12 @@ snapshots: wrappy@1.0.2: {} + ws@8.21.1: {} + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + + yallist@3.1.1: {} + yocto-queue@0.1.0: {} diff --git a/vitest.config.ts b/vitest.config.ts index c1c53b8..6fa0655 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -2,6 +2,6 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { - include: ['apps/**/*.test.ts', 'packages/contracts/**/*.test.ts'], + include: ['apps/**/*.test.{ts,tsx}', 'packages/contracts/**/*.test.ts'], }, });