From ff0b8dfb540f11c67c4344adcb82c7a97e3cc4d6 Mon Sep 17 00:00:00 2001 From: Gabor Szabo <168316277+w7-mgfcode@users.noreply.github.com> Date: Mon, 2 Feb 2026 05:57:27 +0100 Subject: [PATCH 1/6] feat(frontend): Phase 10 Dashboard implementation (#61) * docs(frontend): split INITIAL-11 into three parts with shadcn/ui enhancements Split the dashboard specification into manageable documents: - INITIAL-11A.md: Overview, Tech Stack, Feature descriptions - INITIAL-11B.md: Page Structure, UX flows, wireframes - INITIAL-11C.md: Components, Hooks, Config, Success Criteria Key enhancements: - Simplified app shell to Top Navigation + Tabs pattern - Added validated shadcn/ui component recommendations per section - Updated code snippets to use shadcn primitives (table, chart, collapsible) - Added installation commands and theme configuration - Cross-referenced all three parts with navigation links Co-Authored-By: Claude Opus 4.5 * docs(frontend): restructure INITIAL-11 documents for developer workflow Reorganize the three-part frontend specification so developers can start immediately: - 11A: Setup & Config (installation, dependencies, environment) - 11B: Architecture & Features (tech stack, app shell, feature descriptions) - 11C: Pages & Components (routes, wireframes, code patterns, hooks) All documents now include Documentation Links and Other Considerations sections. Co-Authored-By: Claude Opus 4.5 * feat(frontend): scaffold Vite + React 19 + Tailwind CSS 4 + shadcn/ui project Complete frontend scaffolding per PRP-11A: - Vite + React 19 + TypeScript with strict mode - Tailwind CSS 4 via @tailwindcss/vite plugin - shadcn/ui initialized with New York style (26 components) - Path aliases (@/) configured in vite.config.ts and tsconfig - TanStack Query, TanStack Table, React Router installed - API proxy configured for backend integration - Environment configuration template (.env.example) Co-Authored-By: Claude Opus 4.5 * docs: update documentation for frontend scaffolding - Add Dashboard to Features list in README - Add Node.js/pnpm to prerequisites - Add frontend setup instructions (steps 8-9) - Add Frontend Commands section with dev/build/lint commands - Update project structure to include frontend/ directory - Add Frontend Stack section with technology table - Update ADR-0002 status to Implemented with tech details Co-Authored-By: Claude Opus 4.5 * docs(phase): add Phase 10 Dashboard documentation - Create docs/PHASE/10-DASHBOARD.md with frontend scaffolding details - Document technology stack (React 19, Vite 7, Tailwind CSS 4, shadcn/ui) - Include configuration files, commands, and validation results - Update PHASE-index.md with Phase 10 status (In Progress) - Add sub-phase tracking (10A complete, 10B/10C pending) Co-Authored-By: Claude Opus 4.5 * docs(prp): add PRP-11B dashboard architecture specification Comprehensive PRP for implementing ForecastLab Dashboard architecture including TypeScript types, TanStack Query hooks, DataTable with server-side pagination, charts, WebSocket chat, and theme toggle. Key components: - Full TypeScript types matching backend API schemas - API client with RFC 7807 error handling - TanStack Query v5 hooks with keepPreviousData pattern - DataTable with 1-indexed pagination conversion - WebSocket hook with exponential backoff reconnection - shadcn/ui chart integration Confidence: 8.5/10 Co-Authored-By: Claude Opus 4.5 * feat(frontend): implement dashboard architecture (PRP-11B) Add complete React dashboard implementation including: - TypeScript types for all API responses - API client with typed fetch wrapper and RFC 7807 error handling - TanStack Query hooks for stores, products, KPIs, drilldowns, runs, jobs, RAG sources - WebSocket hook with reconnection and exponential backoff - Theme provider with dark/light mode toggle - App shell with top navigation and mobile drawer - DataTable component with server-side pagination - Common components: StatusBadge, DateRangePicker, ErrorDisplay, LoadingState - Chart components: KPICard, TimeSeriesChart, BacktestFoldsChart - Chat components for agent interaction with tool call display - All page components: Dashboard, Explorer (stores/products/runs/jobs/sales), Visualize (forecast/backtest), Chat, Admin - React Router v7 setup with lazy loading for code splitting Co-Authored-By: Claude Opus 4.5 * docs: update frontend README and Phase 10 documentation - Replace generic Vite template README with comprehensive frontend docs - Document project structure, routes, API integration, and component patterns - Update Phase 10 docs to mark 10B and 10C as complete - Add implementation details for architecture and page deliverables Co-Authored-By: Claude Opus 4.5 * docs(prp): add PRP-11C validation specification Add comprehensive validation PRP for INITIAL-11C (Pages & Components): - 8 validation tasks covering build, routes, components, and UX - 4-level validation loop (build, visual, integration, cross-browser) - 30+ checklist items for thorough testing - Validation report template for documentation - Documents all implemented routes and components Co-Authored-By: Claude Opus 4.5 * feat(frontend): enable LAN access for dev server Set host: true to bind Vite to 0.0.0.0 for mobile/LAN testing. Co-Authored-By: Claude Opus 4.5 * fix: add CORS middleware to enable frontend-backend communication Enable cross-origin requests from Vite dev server to FastAPI backend. This allows the React dashboard to successfully fetch data from API endpoints during development, resolving CORS preflight failures that blocked all frontend integration. Co-Authored-By: Claude Sonnet 4.5 * fix: center layout containers for proper alignment on wide screens - Add mx-auto and px-4 to container classes in app-shell and top-nav - Add LAN IP (10.0.0.121) to CORS allowed origins for development Co-Authored-By: Claude Opus 4.5 * style: format main.py Co-Authored-By: Claude Opus 4.5 --------- Co-authored-by: Gabe@w7dev Co-authored-by: Claude Opus 4.5 --- INITIAL-11A.md | 181 + INITIAL-11B.md | 238 + INITIAL-11C.md | 563 +++ PRPs/PRP-11A-frontend-setup.md | 636 +++ PRPs/PRP-11B-dashboard-architecture.md | 1246 +++++ PRPs/PRP-11C-validation.md | 538 +++ README.md | 104 +- app/main.py | 21 + ...02-frontend-architecture-vite-spa-first.md | 22 +- docs/PHASE-index.md | 26 +- docs/PHASE/10-DASHBOARD.md | 473 ++ frontend/.env.example | 11 + frontend/.gitignore | 29 + frontend/README.md | 256 ++ frontend/components.json | 23 + frontend/eslint.config.js | 30 + frontend/index.html | 13 + frontend/package.json | 60 + frontend/pnpm-lock.yaml | 4059 +++++++++++++++++ frontend/public/vite.svg | 1 + frontend/src/App.tsx | 121 + frontend/src/assets/react.svg | 1 + .../charts/backtest-folds-chart.tsx | 118 + frontend/src/components/charts/index.ts | 3 + frontend/src/components/charts/kpi-card.tsx | 72 + .../components/charts/time-series-chart.tsx | 102 + frontend/src/components/chat/chat-input.tsx | 120 + frontend/src/components/chat/chat-message.tsx | 127 + frontend/src/components/chat/index.ts | 3 + .../src/components/chat/tool-call-display.tsx | 93 + .../components/common/date-range-picker.tsx | 69 + .../src/components/common/error-display.tsx | 78 + frontend/src/components/common/index.ts | 4 + .../src/components/common/loading-state.tsx | 33 + .../src/components/common/status-badge.tsx | 43 + .../data-table/data-table-pagination.tsx | 92 + .../data-table/data-table-toolbar.tsx | 89 + .../src/components/data-table/data-table.tsx | 123 + frontend/src/components/data-table/index.ts | 3 + frontend/src/components/layout/app-shell.tsx | 15 + frontend/src/components/layout/index.ts | 3 + .../src/components/layout/theme-toggle.tsx | 36 + frontend/src/components/layout/top-nav.tsx | 153 + frontend/src/components/ui/accordion.tsx | 66 + frontend/src/components/ui/alert-dialog.tsx | 196 + frontend/src/components/ui/badge.tsx | 48 + frontend/src/components/ui/button.tsx | 64 + frontend/src/components/ui/calendar.tsx | 220 + frontend/src/components/ui/card.tsx | 92 + frontend/src/components/ui/chart.tsx | 355 ++ frontend/src/components/ui/checkbox.tsx | 30 + frontend/src/components/ui/collapsible.tsx | 31 + frontend/src/components/ui/dialog.tsx | 156 + frontend/src/components/ui/dropdown-menu.tsx | 255 ++ frontend/src/components/ui/input.tsx | 21 + .../src/components/ui/navigation-menu.tsx | 168 + frontend/src/components/ui/pagination.tsx | 127 + frontend/src/components/ui/popover.tsx | 89 + frontend/src/components/ui/progress.tsx | 31 + frontend/src/components/ui/scroll-area.tsx | 56 + frontend/src/components/ui/select.tsx | 188 + frontend/src/components/ui/separator.tsx | 28 + frontend/src/components/ui/sheet.tsx | 143 + frontend/src/components/ui/skeleton.tsx | 13 + frontend/src/components/ui/sonner.tsx | 38 + frontend/src/components/ui/table.tsx | 114 + frontend/src/components/ui/tabs.tsx | 89 + frontend/src/components/ui/textarea.tsx | 18 + frontend/src/components/ui/tooltip.tsx | 61 + frontend/src/hooks/index.ts | 8 + frontend/src/hooks/use-drilldowns.ts | 40 + frontend/src/hooks/use-jobs.ts | 69 + frontend/src/hooks/use-kpis.ts | 36 + frontend/src/hooks/use-products.ts | 45 + frontend/src/hooks/use-rag-sources.ts | 35 + frontend/src/hooks/use-runs.ts | 105 + frontend/src/hooks/use-stores.ts | 45 + frontend/src/hooks/use-websocket.ts | 159 + frontend/src/index.css | 142 + frontend/src/lib/api.ts | 106 + frontend/src/lib/constants.ts | 51 + frontend/src/lib/date-utils.ts | 25 + frontend/src/lib/query-client.ts | 15 + frontend/src/lib/status-utils.ts | 20 + frontend/src/lib/utils.ts | 6 + frontend/src/main.tsx | 10 + frontend/src/pages/admin.tsx | 347 ++ frontend/src/pages/chat.tsx | 294 ++ frontend/src/pages/dashboard.tsx | 161 + frontend/src/pages/explorer/jobs.tsx | 192 + frontend/src/pages/explorer/products.tsx | 125 + frontend/src/pages/explorer/runs.tsx | 150 + frontend/src/pages/explorer/sales.tsx | 116 + frontend/src/pages/explorer/stores.tsx | 134 + frontend/src/pages/visualize/backtest.tsx | 223 + frontend/src/pages/visualize/forecast.tsx | 146 + frontend/src/providers/theme-provider.tsx | 19 + frontend/src/types/api.ts | 244 + frontend/src/types/index.ts | 1 + frontend/tsconfig.app.json | 33 + frontend/tsconfig.json | 13 + frontend/tsconfig.node.json | 26 + frontend/vite.config.ts | 27 + uv.lock | 2 +- 104 files changed, 15871 insertions(+), 27 deletions(-) create mode 100644 INITIAL-11A.md create mode 100644 INITIAL-11B.md create mode 100644 INITIAL-11C.md create mode 100644 PRPs/PRP-11A-frontend-setup.md create mode 100644 PRPs/PRP-11B-dashboard-architecture.md create mode 100644 PRPs/PRP-11C-validation.md create mode 100644 docs/PHASE/10-DASHBOARD.md create mode 100644 frontend/.env.example create mode 100644 frontend/.gitignore create mode 100644 frontend/README.md create mode 100644 frontend/components.json create mode 100644 frontend/eslint.config.js create mode 100644 frontend/index.html create mode 100644 frontend/package.json create mode 100644 frontend/pnpm-lock.yaml create mode 100644 frontend/public/vite.svg create mode 100644 frontend/src/App.tsx create mode 100644 frontend/src/assets/react.svg create mode 100644 frontend/src/components/charts/backtest-folds-chart.tsx create mode 100644 frontend/src/components/charts/index.ts create mode 100644 frontend/src/components/charts/kpi-card.tsx create mode 100644 frontend/src/components/charts/time-series-chart.tsx create mode 100644 frontend/src/components/chat/chat-input.tsx create mode 100644 frontend/src/components/chat/chat-message.tsx create mode 100644 frontend/src/components/chat/index.ts create mode 100644 frontend/src/components/chat/tool-call-display.tsx create mode 100644 frontend/src/components/common/date-range-picker.tsx create mode 100644 frontend/src/components/common/error-display.tsx create mode 100644 frontend/src/components/common/index.ts create mode 100644 frontend/src/components/common/loading-state.tsx create mode 100644 frontend/src/components/common/status-badge.tsx create mode 100644 frontend/src/components/data-table/data-table-pagination.tsx create mode 100644 frontend/src/components/data-table/data-table-toolbar.tsx create mode 100644 frontend/src/components/data-table/data-table.tsx create mode 100644 frontend/src/components/data-table/index.ts create mode 100644 frontend/src/components/layout/app-shell.tsx create mode 100644 frontend/src/components/layout/index.ts create mode 100644 frontend/src/components/layout/theme-toggle.tsx create mode 100644 frontend/src/components/layout/top-nav.tsx create mode 100644 frontend/src/components/ui/accordion.tsx create mode 100644 frontend/src/components/ui/alert-dialog.tsx create mode 100644 frontend/src/components/ui/badge.tsx create mode 100644 frontend/src/components/ui/button.tsx create mode 100644 frontend/src/components/ui/calendar.tsx create mode 100644 frontend/src/components/ui/card.tsx create mode 100644 frontend/src/components/ui/chart.tsx create mode 100644 frontend/src/components/ui/checkbox.tsx create mode 100644 frontend/src/components/ui/collapsible.tsx create mode 100644 frontend/src/components/ui/dialog.tsx create mode 100644 frontend/src/components/ui/dropdown-menu.tsx create mode 100644 frontend/src/components/ui/input.tsx create mode 100644 frontend/src/components/ui/navigation-menu.tsx create mode 100644 frontend/src/components/ui/pagination.tsx create mode 100644 frontend/src/components/ui/popover.tsx create mode 100644 frontend/src/components/ui/progress.tsx create mode 100644 frontend/src/components/ui/scroll-area.tsx create mode 100644 frontend/src/components/ui/select.tsx create mode 100644 frontend/src/components/ui/separator.tsx create mode 100644 frontend/src/components/ui/sheet.tsx create mode 100644 frontend/src/components/ui/skeleton.tsx create mode 100644 frontend/src/components/ui/sonner.tsx create mode 100644 frontend/src/components/ui/table.tsx create mode 100644 frontend/src/components/ui/tabs.tsx create mode 100644 frontend/src/components/ui/textarea.tsx create mode 100644 frontend/src/components/ui/tooltip.tsx create mode 100644 frontend/src/hooks/index.ts create mode 100644 frontend/src/hooks/use-drilldowns.ts create mode 100644 frontend/src/hooks/use-jobs.ts create mode 100644 frontend/src/hooks/use-kpis.ts create mode 100644 frontend/src/hooks/use-products.ts create mode 100644 frontend/src/hooks/use-rag-sources.ts create mode 100644 frontend/src/hooks/use-runs.ts create mode 100644 frontend/src/hooks/use-stores.ts create mode 100644 frontend/src/hooks/use-websocket.ts create mode 100644 frontend/src/index.css create mode 100644 frontend/src/lib/api.ts create mode 100644 frontend/src/lib/constants.ts create mode 100644 frontend/src/lib/date-utils.ts create mode 100644 frontend/src/lib/query-client.ts create mode 100644 frontend/src/lib/status-utils.ts create mode 100644 frontend/src/lib/utils.ts create mode 100644 frontend/src/main.tsx create mode 100644 frontend/src/pages/admin.tsx create mode 100644 frontend/src/pages/chat.tsx create mode 100644 frontend/src/pages/dashboard.tsx create mode 100644 frontend/src/pages/explorer/jobs.tsx create mode 100644 frontend/src/pages/explorer/products.tsx create mode 100644 frontend/src/pages/explorer/runs.tsx create mode 100644 frontend/src/pages/explorer/sales.tsx create mode 100644 frontend/src/pages/explorer/stores.tsx create mode 100644 frontend/src/pages/visualize/backtest.tsx create mode 100644 frontend/src/pages/visualize/forecast.tsx create mode 100644 frontend/src/providers/theme-provider.tsx create mode 100644 frontend/src/types/api.ts create mode 100644 frontend/src/types/index.ts create mode 100644 frontend/tsconfig.app.json create mode 100644 frontend/tsconfig.json create mode 100644 frontend/tsconfig.node.json create mode 100644 frontend/vite.config.ts diff --git a/INITIAL-11A.md b/INITIAL-11A.md new file mode 100644 index 00000000..53c09a65 --- /dev/null +++ b/INITIAL-11A.md @@ -0,0 +1,181 @@ +# INITIAL-11A.md — ForecastLab Dashboard (The Face) + +> **Part A of 3**: Setup & Configuration +> See also: [INITIAL-11B.md](./INITIAL-11B.md) (Architecture & Features) | [INITIAL-11C.md](./INITIAL-11C.md) (Pages & Components) + +--- + +## Prerequisites + +- Node.js 20+ +- pnpm (recommended) or npm +- Backend API running on `http://localhost:8123` + +--- + +## Project Structure + +``` +frontend/ +├── src/ +│ ├── components/ +│ │ ├── ui/ # shadcn/ui components (auto-generated) +│ │ ├── data-table/ # DataTable wrapper +│ │ ├── charts/ # Chart components +│ │ ├── chat/ # Chat components +│ │ └── layout/ # App shell, nav +│ ├── hooks/ # TanStack Query hooks +│ ├── lib/ +│ │ ├── api.ts # API client +│ │ └── utils.ts # cn() utility +│ ├── pages/ # Route pages +│ └── App.tsx +├── .env.example +├── package.json +├── tailwind.config.js +└── vite.config.ts +``` + +--- + +## Installation + +### Step 1: Create Project + +```bash +pnpm create vite frontend --template react-ts +cd frontend +``` + +### Step 2: Install Dependencies + +```bash +# Core dependencies +pnpm add @tanstack/react-query @tanstack/react-table recharts date-fns lucide-react + +# Tailwind CSS 4 +pnpm add -D tailwindcss @tailwindcss/vite +npx tailwindcss init +``` + +### Step 3: Initialize shadcn/ui + +```bash +npx shadcn@latest init +``` + +When prompted: +- Style: **New York** +- Base color: **Neutral** +- CSS variables: **Yes** + +### Step 4: Install shadcn Components + +```bash +# Layout & Navigation +npx shadcn@latest add card tabs navigation-menu sheet scroll-area separator + +# Data Display +npx shadcn@latest add table badge skeleton pagination progress + +# Form Components +npx shadcn@latest add button input select textarea calendar popover checkbox + +# Feedback & Overlays +npx shadcn@latest add sonner tooltip alert-dialog dialog + +# Interactive +npx shadcn@latest add collapsible accordion dropdown-menu + +# Charts (wraps Recharts) +npx shadcn@latest add chart +``` + +--- + +## Environment Configuration + +Create `.env` from `.env.example`: + +```env +# .env.example + +# API Configuration +VITE_API_BASE_URL=http://localhost:8123 +VITE_WS_URL=ws://localhost:8123/agents/stream + +# Feature Flags +VITE_ENABLE_AGENT_CHAT=true +VITE_ENABLE_ADMIN_PANEL=true + +# Visualization +VITE_DEFAULT_PAGE_SIZE=25 +VITE_MAX_CHART_POINTS=365 +``` + +--- + +## Theme Configuration + +Add chart color variables to `src/index.css`: + +```css +@layer base { + :root { + --chart-1: 221.2 83.2% 53.3%; + --chart-2: 142.1 76.2% 36.3%; + --chart-3: 47.9 95.8% 53.1%; + --chart-4: 24.6 95% 53.1%; + --chart-5: 280.1 93.6% 53.1%; + } + + .dark { + --chart-1: 217.2 91.2% 59.8%; + --chart-2: 142.1 70.6% 45.3%; + --chart-3: 47.9 95.8% 53.1%; + --chart-4: 24.6 95% 53.1%; + --chart-5: 280.1 93.6% 53.1%; + } +} +``` + +--- + +## Verify Setup + +```bash +# Start dev server +pnpm dev + +# Open http://localhost:5173 +``` + +You should see the Vite + React welcome page. shadcn/ui components are now available in `src/components/ui/`. + +--- + +## Documentation Links + +- [shadcn/ui Documentation](https://ui.shadcn.com/) +- [shadcn/ui Installation](https://ui.shadcn.com/docs/installation) +- [Vite Documentation](https://vite.dev/) +- [TanStack Query](https://tanstack.com/query/latest) +- [TanStack Table](https://tanstack.com/table/latest) +- [Tailwind CSS 4](https://tailwindcss.com/) + +--- + +## Other Considerations + +- **Node Version**: Use Node.js 20+ for best compatibility +- **Package Manager**: pnpm is recommended for faster installs +- **TypeScript**: Strict mode enabled by default +- **Path Aliases**: `@/` maps to `src/` via tsconfig +- **CSS Variables**: shadcn/ui uses CSS variables for theming + +--- + +## Next Steps + +1. **Read [INITIAL-11B.md](./INITIAL-11B.md)** — Understand the architecture and features +2. **Read [INITIAL-11C.md](./INITIAL-11C.md)** — Implement pages and components diff --git a/INITIAL-11B.md b/INITIAL-11B.md new file mode 100644 index 00000000..03e43036 --- /dev/null +++ b/INITIAL-11B.md @@ -0,0 +1,238 @@ +# INITIAL-11B.md — ForecastLab Dashboard (The Face) + +> **Part B of 3**: Architecture & Features +> See also: [INITIAL-11A.md](./INITIAL-11A.md) (Setup & Config) | [INITIAL-11C.md](./INITIAL-11C.md) (Pages & Components) + +--- + +## Architectural Role + +**"The Face"** — User interface, data visualization, and agent interaction using React 19 + shadcn/ui. + +This phase provides the visual layer for: +- Data exploration with server-side pagination and filtering +- Time series visualization with interactive charts +- Agent chat interface with streaming responses +- Admin panel for system management + +--- + +## Tech Stack + +| Component | Technology | Purpose | +|-----------|------------|---------| +| Framework | React 19 + [Vite](https://vite.dev/) | Fast build, HMR | +| Components | [shadcn/ui](https://ui.shadcn.com/) | Accessible, customizable UI | +| Data Tables | [TanStack Table](https://tanstack.com/table/latest) | Server-side data grids | +| Data Fetching | [TanStack Query](https://tanstack.com/query/latest) | Caching, invalidation | +| Charts | [Recharts](https://recharts.org/) via shadcn/ui Chart | Time series visualization | +| Styling | Tailwind CSS 4 | Utility-first CSS | +| State | React 19 `use()` + TanStack Query | Server state management | + +--- + +## App Shell Architecture + +**Decision: Top Navigation + Tabs (Simple Implementation)** + +Instead of a complex sidebar system, use a minimal top navigation bar with route-level tabs: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ [Logo] ForecastLab [Dashboard] [Explorer] [Visualize] │ +│ [Chat] [Admin] [Theme] [?] │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ [Page Content with optional Tabs for sub-routes] │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +**Desktop:** Top `navigation-menu` + route-level `tabs` where relevant +**Mobile:** `sheet` component for hamburger menu navigation + +**shadcn components for App Shell:** +- `navigation-menu` — Main nav bar +- `sheet` — Mobile nav drawer +- `tabs` — Sub-route navigation (e.g., /explorer/sales vs /explorer/runs) +- `button` — Theme toggle, action buttons +- `dropdown-menu` — User menu, settings + +--- + +## Features + +### Data Explorer + +Interactive data tables with full server-side capabilities: +- **Tables**: Sales, Stores, Products, Model Runs, Jobs +- **Features**: Pagination, sorting, filtering, column visibility +- **Export**: CSV download for selected/all rows +- **Pattern**: [shadcn/ui Data Table](https://ui.shadcn.com/docs/components/data-table) + +**shadcn components:** +- `table` (Table, TableHeader, TableBody, TableRow, TableCell, TableHead) +- `checkbox` — Row selection +- `dropdown-menu` — Column visibility, row actions +- `input` — Filter inputs +- `select` — Filter dropdowns +- `button` — Pagination, export actions +- `badge` — Status columns +- `skeleton` — Loading rows + +### Time Series Visualizers + +Charts for forecasting analysis: +- **Actual vs Predicted**: Line chart with confidence intervals +- **Backtest Folds**: Train/test split visualization +- **Metric Comparison**: Bar charts for model comparison +- **Interactive**: Tooltips, zoom, pan, brush selection + +**shadcn components:** +- `chart` (ChartContainer, ChartTooltip, ChartTooltipContent, ChartConfig) +- `card` — Chart wrapper with header/footer +- `select` — Chart controls (store, product, model) +- `badge` — Metric display +- `tabs` — Switch between chart views + +### Agent Chat Interface + +Real-time interaction with AI agents: +- **Streaming**: WebSocket-based token streaming +- **Citations**: Rendered with source links +- **Tool Calls**: Collapsible visualization of agent actions +- **History**: Session sidebar with conversation threads + +**shadcn components:** +- `card` — Message bubbles +- `scroll-area` — Chat message list +- `collapsible` — Tool call details +- `accordion` — Citation groups +- `textarea` — Message input +- `button` — Send button +- `badge` — Source type indicators +- `skeleton` — Streaming placeholder + +### Admin Panel + +System management and monitoring: +- **RAG Sources**: Index/delete documentation sources +- **Model Aliases**: Manage deployment aliases +- **Health Dashboard**: Service status, recent errors +- **Job Monitor**: Active and historical job status + +**shadcn components:** +- `card` — Section containers +- `table` — Source/alias/job lists +- `alert-dialog` — Delete confirmations +- `dialog` — Create/edit modals +- `badge` — Status indicators +- `progress` — Job progress +- `sonner` — Action feedback toasts + +--- + +## shadcn/ui Components Summary + +### Layout & Navigation +| Component | Usage | +|-----------|-------| +| `tabs` | Route-level section switching | +| `navigation-menu` | Top navigation bar | +| `sheet` | Mobile navigation drawer | +| `card` | Content containers, KPI cards | +| `scroll-area` | Scrollable content areas | +| `separator` | Visual dividers | + +### Data Display +| Component | Usage | +|-----------|-------| +| `table` | Data table primitives | +| `badge` | Status indicators (SUCCESS, FAILED, PENDING) | +| `skeleton` | Loading placeholders | +| `pagination` | Table pagination controls | +| `progress` | Progress bars | + +### Forms & Inputs +| Component | Usage | +|-----------|-------| +| `button` | Action buttons with variants | +| `input` | Text inputs | +| `select` | Dropdown selects | +| `textarea` | Multiline inputs | +| `calendar` | Date picker | +| `popover` | Date range picker wrapper | +| `checkbox` | Row selection in tables | + +### Feedback & Overlays +| Component | Usage | +|-----------|-------| +| `sonner` | Toast notifications | +| `tooltip` | Hover hints | +| `alert-dialog` | Confirmation dialogs | +| `dialog` | Modal dialogs | + +### Interactive +| Component | Usage | +|-----------|-------| +| `collapsible` | Tool call/citation sections | +| `accordion` | Expandable content groups | +| `dropdown-menu` | Action menus, column visibility | + +### Charts +| Component | Usage | +|-----------|-------| +| `chart` | ChartContainer, ChartTooltip (wraps Recharts) | + +--- + +## Cross-Module Integration + +| Direction | Module | Integration Point | +|-----------|--------|-------------------| +| **← RAG Layer** | INITIAL-9 | Displays indexed sources, allows re-indexing | +| **← Agentic Layer** | INITIAL-10 | Chat interface, experiment status display | +| **← Registry** | Phase 6 | Run leaderboard, comparison views | +| **← Analytics** | Phase 7 | KPI dashboard, drilldown charts | +| **← Jobs** | Phase 7 | Job status monitoring | +| **← Dimensions** | Phase 7 | Store/product selectors | + +--- + +## Success Criteria + +- [ ] Data tables handle 10k+ rows with virtual scrolling +- [ ] Server-side pagination, sorting, filtering all functional +- [ ] Charts render smoothly with 365+ data points +- [ ] WebSocket chat shows streaming tokens in real-time +- [ ] Citations render as clickable source links +- [ ] Tool calls displayed in collapsible sections +- [ ] Responsive design works on tablet and mobile +- [ ] Lighthouse performance score > 90 +- [ ] Accessibility: keyboard navigation, screen reader support +- [ ] Dark/light theme toggle + +--- + +## Other Considerations + +- **No Hardcoded URLs**: API base URL from environment variable only +- **Error Boundaries**: Graceful error handling with retry options +- **Loading States**: Skeleton components for all async data +- **Optimistic Updates**: Instant UI feedback for mutations +- **Caching**: TanStack Query manages cache invalidation +- **Bundle Size**: Code splitting per route for fast initial load + +--- + +## Documentation Links + +- [shadcn/ui Documentation](https://ui.shadcn.com/) +- [shadcn/ui Data Table](https://ui.shadcn.com/docs/components/data-table) +- [shadcn/ui Charts](https://ui.shadcn.com/docs/components/chart) +- [TanStack Table](https://tanstack.com/table/latest) +- [TanStack Query](https://tanstack.com/query/latest) +- [Recharts](https://recharts.org/) +- [Vite Documentation](https://vite.dev/) +- [React 19 Documentation](https://react.dev/) +- [Tailwind CSS 4](https://tailwindcss.com/) diff --git a/INITIAL-11C.md b/INITIAL-11C.md new file mode 100644 index 00000000..e0496603 --- /dev/null +++ b/INITIAL-11C.md @@ -0,0 +1,563 @@ +# INITIAL-11C.md — ForecastLab Dashboard (The Face) + +> **Part C of 3**: Pages & Components +> See also: [INITIAL-11A.md](./INITIAL-11A.md) (Setup & Config) | [INITIAL-11B.md](./INITIAL-11B.md) (Architecture & Features) + +--- + +## Route Overview + +| Route | Description | API Endpoint | +|-------|-------------|--------------| +| `/dashboard` | KPI summary cards | GET /analytics/kpis | +| `/explorer/sales` | Sales data table | GET /analytics/drilldowns | +| `/explorer/runs` | Model run table | GET /registry/runs | +| `/visualize/forecast` | Forecast chart | GET /forecasting/predict | +| `/visualize/backtest` | Backtest folds | GET /backtesting/results/{run_id} | +| `/chat` | Agent chat | WS /agents/stream | +| `/admin` | Admin panel | GET /rag/sources, /registry/aliases | + +--- + +## Page Wireframes + +### /dashboard + +``` +┌─────────────────────────────────────────────────────────────┐ +│ [Logo] ForecastLab [Dashboard] [Explorer▼] [Visualize▼] │ +│ [Chat] [Admin] [Theme] [?] │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ Total Sales │ │ Active Runs │ │ RAG Sources │ │ +│ │ $2.4M │ │ 127 │ │ 15 │ │ +│ │ +12.3% │ │ +5 today │ │ indexed │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ │ +│ │ +│ ┌───────────────────────────────────────────────────────┐ │ +│ │ Recent Activity [See All]│ +│ │ • Backtest run_abc completed (2h ago) │ +│ │ • Model alias "production" updated (5h ago) │ +│ └───────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### /explorer/sales + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Sales Explorer [Export] │ +├─────────────────────────────────────────────────────────────┤ +│ Filters: [Date Range] [Store ▼] [Product ▼] [Search...] │ +├─────────────────────────────────────────────────────────────┤ +│ Date │ Store │ Product │ Quantity │ Revenue │ +│ 2026-01-15 │ S001 │ P001 │ 150 │ $2,250.00 │ +│ 2026-01-15 │ S001 │ P002 │ 75 │ $1,125.00 │ +│ ... │ ... │ ... │ ... │ ... │ +├─────────────────────────────────────────────────────────────┤ +│ Page 1 of 50 │ [< Prev] [1] [2] [3] ... [50] [Next >] │ +└─────────────────────────────────────────────────────────────┘ +``` + +### /explorer/runs + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Model Runs [Compare Selected] │ +├─────────────────────────────────────────────────────────────┤ +│ Filters: [Model Type ▼] [Status ▼] [Store ▼] [Product ▼] │ +├─────────────────────────────────────────────────────────────┤ +│ [☐] │ Run ID │ Model │ Status │ MAE │ Created │ +│ [☐] │ run_abc │ MA(14) │ SUCCESS │ 12.5 │ 2h ago │ +│ [☐] │ run_def │ SN(7) │ SUCCESS │ 15.2 │ 3h ago │ +│ [☐] │ run_ghi │ Naive │ SUCCESS │ 18.9 │ 5h ago │ +├─────────────────────────────────────────────────────────────┤ +│ Showing 3 of 127 runs │ +└─────────────────────────────────────────────────────────────┘ +``` + +### /visualize/forecast + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Forecast: Store S001, Product P001 │ +├─────────────────────────────────────────────────────────────┤ +│ [Store ▼] [Product ▼] [Model Run ▼] [Date Range] │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ 200 ─┤ ╭────── │ +│ │ ╭────╯ Predicted │ +│ 150 ─┤ ╭────╯ │ +│ │ ╭────╯ ───── Actual │ +│ 100 ─┤ ╭────╯ - - - Confidence │ +│ │ ╭────╯ │ +│ 50 ─┤ ╭────╯ │ +│ │─╯ │ +│ 0 ─┼──────────────────────────────────────────────── │ +│ Jan 1 Jan 15 Feb 1 Feb 15 Mar 1 │ +│ │ +├─────────────────────────────────────────────────────────────┤ +│ MAE: 12.5 │ sMAPE: 15.2% │ WAPE: 8.1% │ Bias: -2.3 │ +└─────────────────────────────────────────────────────────────┘ +``` + +### /visualize/backtest + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Backtest: run_abc123 (5-fold Expanding Window) │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ Fold 1: ████████████░░░░ MAE: 14.2 sMAPE: 16.8% │ +│ Fold 2: █████████████████░░░░ MAE: 13.1 sMAPE: 15.4% │ +│ Fold 3: ███████████████████████░░░░ MAE: 12.8 sMAPE: 14.9│ +│ Fold 4: █████████████████████████████░░░░ MAE: 11.9 │ +│ Fold 5: ███████████████████████████████████░░░░ MAE: 11.2│ +│ │ +│ █ Train ░ Test │ +├─────────────────────────────────────────────────────────────┤ +│ Aggregated: MAE: 12.6 ± 1.1 │ Stability: 0.91 │ +└─────────────────────────────────────────────────────────────┘ +``` + +### /chat + +``` +┌─────────────────────────────────────────────────────────────┐ +│ ForecastLab Assistant │ +├────────────┬────────────────────────────────────────────────┤ +│ Sessions │ │ +│ ─────────│ How does backtesting prevent data leakage? │ +│ Today │ │ +│ ◉ Current │ The backtesting module prevents data leakage │ +│ ○ 10:30am │ through several mechanisms: │ +│ ○ 9:15am │ │ +│ Yesterday │ 1. **Time-based splits**: Uses expanding... │ +│ ○ 4:45pm │ │ +│ │ 📚 Citations: │ +│ │ [1] docs/PHASE/5-BACKTESTING.md │ +│ │ [2] CLAUDE.md │ +│ │ │ +│ │ ────────────────────────────────────────── │ +│ │ 🔧 Tool: retrieve_context (5 chunks found) │ +│ │ ────────────────────────────────────────── │ +├────────────┴────────────────────────────────────────────────┤ +│ [Type your question...] [Send ➤] │ +└─────────────────────────────────────────────────────────────┘ +``` + +### /admin + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Admin Panel │ +├─────────────────────────────────────────────────────────────┤ +│ [RAG Sources] [Model Aliases] [Jobs] [Health] │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ RAG Sources [+ Index New] │ +│ ┌─────────────────────────────────────────────────────────┐│ +│ │ Source │ Type │ Chunks │ Indexed │ ⋮ ││ +│ │ CLAUDE.md │ markdown │ 45 │ 2h ago │ [⋮] ││ +│ │ README.md │ markdown │ 23 │ 1d ago │ [⋮] ││ +│ │ openapi.yaml │ openapi │ 78 │ 3d ago │ [⋮] ││ +│ └─────────────────────────────────────────────────────────┘│ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## Component Patterns + +### DataTable + +Uses [shadcn/ui Data Table](https://ui.shadcn.com/docs/components/data-table) with TanStack Table. + +```tsx +// components/data-table/data-table.tsx +"use client" + +import { + ColumnDef, + flexRender, + getCoreRowModel, + useReactTable, + type PaginationState, +} from "@tanstack/react-table" +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table" +import { Button } from "@/components/ui/button" +import { Skeleton } from "@/components/ui/skeleton" + +interface DataTableProps { + columns: ColumnDef[] + data: TData[] + pageCount: number + pagination: PaginationState + onPaginationChange: (pagination: PaginationState) => void + isLoading?: boolean +} + +export function DataTable({ + columns, data, pageCount, pagination, onPaginationChange, isLoading, +}: DataTableProps) { + const table = useReactTable({ + data, + columns, + pageCount, + state: { pagination }, + onPaginationChange: (updater) => { + const next = typeof updater === "function" ? updater(pagination) : updater + onPaginationChange(next) + }, + manualPagination: true, + getCoreRowModel: getCoreRowModel(), + }) + + return ( +
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {flexRender(header.column.columnDef.header, header.getContext())} + + ))} + + ))} + + + {isLoading ? ( + Array.from({ length: pagination.pageSize }).map((_, i) => ( + + {columns.map((_, j) => ( + + ))} + + )) + ) : table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + ))} + +
+
+
+ + +
+
+ ) +} +``` + +### TimeSeriesChart + +Uses shadcn/ui `chart` wrapping Recharts. + +```tsx +// components/charts/time-series-chart.tsx +"use client" + +import { CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts" +import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" +import { ChartContainer, ChartTooltip, ChartTooltipContent, type ChartConfig } from "@/components/ui/chart" +import { Badge } from "@/components/ui/badge" + +const chartConfig = { + actual: { label: "Actual", color: "var(--chart-1)" }, + predicted: { label: "Predicted", color: "var(--chart-2)" }, +} satisfies ChartConfig + +interface Props { + title: string + data: { date: string; actual: number; predicted?: number }[] + metrics?: { mae?: number; smape?: number } +} + +export function TimeSeriesChart({ title, data, metrics }: Props) { + return ( + + {title} + + + + + + + } /> + + + + + + {metrics && ( + + {metrics.mae && MAE: {metrics.mae.toFixed(1)}} + {metrics.smape && sMAPE: {metrics.smape.toFixed(1)}%} + + )} + + ) +} +``` + +### ChatMessage + +Uses `collapsible` for tool calls. + +```tsx +// components/chat/chat-message.tsx +"use client" + +import { cn } from "@/lib/utils" +import { Card, CardContent } from "@/components/ui/card" +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible" + +interface Props { + role: "user" | "assistant" + content: string + citations?: { id: string; sourcePath: string }[] + toolCalls?: { id: string; name: string }[] + isStreaming?: boolean +} + +export function ChatMessage({ role, content, citations, toolCalls, isStreaming }: Props) { + return ( +
+ + +
+ {content} + {isStreaming && } +
+ + {citations?.length > 0 && ( +
+

Citations:

+
+ {citations.map((c) => ( + [{c.id}] {c.sourcePath} + ))} +
+
+ )} + + {toolCalls?.length > 0 && ( + + + + + + {toolCalls.map((tc) => ( +
{tc.name}
+ ))} +
+
+ )} +
+
+
+ ) +} +``` + +### DateRangePicker + +Uses `popover` + `calendar`. + +```tsx +// components/date-range-picker.tsx +"use client" + +import { format } from "date-fns" +import { CalendarIcon } from "lucide-react" +import { type DateRange } from "react-day-picker" +import { Button } from "@/components/ui/button" +import { Calendar } from "@/components/ui/calendar" +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover" + +interface Props { + value?: DateRange + onChange: (range: DateRange | undefined) => void +} + +export function DateRangePicker({ value, onChange }: Props) { + return ( + + + + + + + + + ) +} +``` + +### StatusBadge + +```tsx +// components/status-badge.tsx +import { Badge } from "@/components/ui/badge" + +const variants = { + SUCCESS: "default", + FAILED: "destructive", + RUNNING: "secondary", + PENDING: "outline", +} as const + +export function StatusBadge({ status }: { status: keyof typeof variants }) { + return {status} +} +``` + +--- + +## API Hooks + +```tsx +// hooks/use-sales.ts +import { useQuery, keepPreviousData } from "@tanstack/react-query" +import { api } from "@/lib/api" + +export function useSales(params: { page: number; pageSize: number; storeId?: number }) { + return useQuery({ + queryKey: ["sales", params], + queryFn: () => api.get("/analytics/drilldowns", { params }), + placeholderData: keepPreviousData, + }) +} + +// hooks/use-runs.ts +export function useRuns(params: { page: number; pageSize: number; status?: string }) { + return useQuery({ + queryKey: ["runs", params], + queryFn: () => api.get("/registry/runs", { params }), + }) +} + +// hooks/use-chat.ts +import { useState, useCallback, useEffect, useRef } from "react" + +export function useChat() { + const [messages, setMessages] = useState<{ id: string; role: "user" | "assistant"; content: string }[]>([]) + const [isConnected, setIsConnected] = useState(false) + const wsRef = useRef(null) + + useEffect(() => { + const ws = new WebSocket(import.meta.env.VITE_WS_URL) + wsRef.current = ws + ws.onopen = () => setIsConnected(true) + ws.onclose = () => setIsConnected(false) + ws.onmessage = (event) => { + const data = JSON.parse(event.data) + if (data.type === "token") { + setMessages((prev) => { + const last = prev[prev.length - 1] + if (last?.role === "assistant") { + return [...prev.slice(0, -1), { ...last, content: last.content + data.token }] + } + return [...prev, { id: crypto.randomUUID(), role: "assistant", content: data.token }] + }) + } + } + return () => ws.close() + }, []) + + const sendMessage = useCallback((content: string) => { + if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) return + setMessages((prev) => [...prev, { id: crypto.randomUUID(), role: "user", content }]) + wsRef.current.send(JSON.stringify({ type: "query", agent: "rag_assistant", payload: { query: content } })) + }, []) + + return { messages, sendMessage, isConnected } +} +``` + +--- + +## Delete Confirmation Pattern + +```tsx +// Used in Admin panel for destructive actions + + + Delete + + + + Delete Source? + + This will remove "{sourceName}" and all its indexed chunks. This action cannot be undone. + + + + Cancel + Delete + + + +``` + +--- + +## Documentation Links + +- [shadcn/ui Data Table](https://ui.shadcn.com/docs/components/data-table) +- [shadcn/ui Charts](https://ui.shadcn.com/docs/components/chart) +- [TanStack Table Server-Side](https://tanstack.com/table/latest/docs/guide/pagination#manual-server-side-pagination) +- [TanStack Query](https://tanstack.com/query/latest) +- [Recharts](https://recharts.org/) +- [React Day Picker](https://react-day-picker.js.org/) + +--- + +## Other Considerations + +- **Server-Side Operations**: All pagination, sorting, filtering is manual (server-side) +- **Loading States**: Use `Skeleton` for all async data +- **Error Handling**: Wrap pages in error boundaries +- **Accessibility**: All components support keyboard navigation +- **Mobile**: Use `sheet` for navigation, responsive tables +- **Bundle Size**: Code split by route for fast initial load + +--- + +## Running the Dashboard + +```bash +cd frontend +pnpm install +pnpm dev +``` + +Open http://localhost:5173 diff --git a/PRPs/PRP-11A-frontend-setup.md b/PRPs/PRP-11A-frontend-setup.md new file mode 100644 index 00000000..730e068b --- /dev/null +++ b/PRPs/PRP-11A-frontend-setup.md @@ -0,0 +1,636 @@ +# PRP-11A: ForecastLab Frontend Setup & Configuration + +**Feature**: INITIAL-11A.md — Frontend Project Scaffolding +**Status**: Ready for Implementation +**Confidence Score**: 9.5/10 + +--- + +## Goal + +Scaffold a complete Vite + React 19 + TypeScript frontend project with: +1. **Tailwind CSS 4** via `@tailwindcss/vite` plugin +2. **shadcn/ui** component library initialized with New York style +3. **All required shadcn components** pre-installed for dashboard features +4. **Environment configuration** for backend API integration +5. **TypeScript strict mode** with proper path aliases + +This is a **setup-only PRP** — no application code, just project scaffolding and configuration files. + +--- + +## Why + +- **Developer Experience**: Frontend developers can immediately start building features +- **Consistency**: Pre-configured project ensures consistent patterns across the team +- **Portfolio Value**: Demonstrates modern React 19 + Tailwind CSS 4 setup +- **Dependency Management**: All shadcn/ui components installed upfront to avoid mid-development interruptions + +--- + +## What + +### Deliverables + +| File/Directory | Purpose | +|----------------|---------| +| `frontend/` | Root directory for React project | +| `frontend/package.json` | Dependencies and scripts | +| `frontend/vite.config.ts` | Vite + Tailwind + path aliases | +| `frontend/tsconfig.json` | TypeScript config with path aliases | +| `frontend/tsconfig.app.json` | App-specific TypeScript config | +| `frontend/src/index.css` | Tailwind CSS import + theme variables | +| `frontend/src/lib/utils.ts` | `cn()` utility (auto-generated by shadcn) | +| `frontend/components.json` | shadcn/ui configuration | +| `frontend/src/components/ui/` | All shadcn/ui component files | +| `frontend/.env.example` | Environment variable template | +| `frontend/.gitignore` | Node.js ignores | + +### Success Criteria + +- [ ] `pnpm create vite` completes successfully with react-ts template +- [ ] `pnpm install` installs all dependencies without errors +- [ ] `npx shadcn@latest init` creates components.json +- [ ] All 21 shadcn components install without errors +- [ ] `pnpm dev` starts dev server on http://localhost:5173 +- [ ] `pnpm build` produces production bundle without TypeScript errors +- [ ] Path alias `@/` resolves correctly in imports +- [ ] Tailwind CSS classes apply correctly + +--- + +## All Needed Context + +### Documentation & References + +```yaml +# Vite + React Setup +- url: https://vite.dev/guide/ + why: "Vite project scaffolding, environment variables (import.meta.env)" + section: "Getting Started, Env Variables and Modes" + +# Tailwind CSS 4 with Vite (CRITICAL) +- url: https://tailwindcss.com/docs + why: "Tailwind CSS 4 installation with @tailwindcss/vite plugin" + critical: "Use @import 'tailwindcss' instead of @tailwind directives" + +# shadcn/ui Installation (CRITICAL) +- url: https://ui.shadcn.com/docs/installation/vite + why: "Official Vite installation guide" + critical: "Use 'npx shadcn@latest init' NOT 'npx shadcn-ui init'" + +# shadcn/ui Components +- url: https://ui.shadcn.com/docs/components + why: "Component reference for all 21 components to install" + +# TypeScript Path Aliases +- url: https://vite.dev/guide/features.html#typescript + why: "Path alias configuration with @/ mapping to src/" +``` + +### Current Codebase Tree + +``` +ForecastLabAI/ +├── app/ # FastAPI backend (existing) +├── docs/ +├── examples/ +├── PRPs/ +├── INITIAL-11A.md # This spec +├── INITIAL-11B.md # Architecture (next phase) +├── INITIAL-11C.md # Pages (next phase) +├── .gitignore # Already has node_modules +└── docker-compose.yml +``` + +### Desired Codebase Tree (Files to Create) + +``` +frontend/ # NEW: Entire directory +├── public/ +│ └── vite.svg # Auto-generated by Vite +├── src/ +│ ├── components/ +│ │ └── ui/ # shadcn/ui components (21 files) +│ │ ├── accordion.tsx +│ │ ├── alert-dialog.tsx +│ │ ├── badge.tsx +│ │ ├── button.tsx +│ │ ├── calendar.tsx +│ │ ├── card.tsx +│ │ ├── chart.tsx +│ │ ├── checkbox.tsx +│ │ ├── collapsible.tsx +│ │ ├── dialog.tsx +│ │ ├── dropdown-menu.tsx +│ │ ├── input.tsx +│ │ ├── navigation-menu.tsx +│ │ ├── pagination.tsx +│ │ ├── popover.tsx +│ │ ├── progress.tsx +│ │ ├── scroll-area.tsx +│ │ ├── select.tsx +│ │ ├── separator.tsx +│ │ ├── sheet.tsx +│ │ ├── skeleton.tsx +│ │ ├── sonner.tsx +│ │ ├── table.tsx +│ │ ├── tabs.tsx +│ │ ├── textarea.tsx +│ │ └── tooltip.tsx +│ ├── lib/ +│ │ └── utils.ts # cn() utility (shadcn auto-generates) +│ ├── App.tsx # Vite default (will be replaced in 11B/C) +│ ├── main.tsx # Entry point +│ └── index.css # Tailwind imports + chart theme +├── .env.example # Environment template +├── .gitignore # Node.js ignores +├── components.json # shadcn/ui config +├── eslint.config.js # Vite default +├── index.html # Vite default +├── package.json # Dependencies +├── tsconfig.json # TypeScript config +├── tsconfig.app.json # App TypeScript config +├── tsconfig.node.json # Node TypeScript config +└── vite.config.ts # Vite + Tailwind + aliases +``` + +### Known Gotchas & Library Quirks + +```typescript +// CRITICAL: Tailwind CSS 4 syntax change +// OLD (v3): @tailwind base; @tailwind components; @tailwind utilities; +// NEW (v4): @import "tailwindcss"; +// The single import replaces all three directives + +// CRITICAL: shadcn/ui init command +// CORRECT: npx shadcn@latest init +// WRONG: npx shadcn-ui init (deprecated package name) + +// CRITICAL: Vite environment variables +// CORRECT: import.meta.env.VITE_API_BASE_URL +// WRONG: process.env.VITE_API_BASE_URL (that's Node.js) +// Variables MUST be prefixed with VITE_ to be exposed to client + +// CRITICAL: TypeScript path aliases require BOTH configs +// Must add "paths" to BOTH tsconfig.json AND tsconfig.app.json +// Also need vite.config.ts resolve.alias for runtime + +// CRITICAL: @types/node for path module +// vite.config.ts uses path.resolve() which requires @types/node +// Install: pnpm add -D @types/node + +// CRITICAL: shadcn/ui chart component requires recharts +// When installing chart component, it auto-installs recharts +// BUT TanStack libraries need manual install: pnpm add @tanstack/react-query @tanstack/react-table + +// CRITICAL: date-fns and lucide-react +// shadcn components use these as peer dependencies +// Calendar needs date-fns, icons need lucide-react +// Ensure: pnpm add date-fns lucide-react +``` + +--- + +## Implementation Blueprint + +### Task Overview + +| # | Task | Description | Validation | +|---|------|-------------|------------| +| 1 | Create Vite project | `pnpm create vite frontend --template react-ts` | Directory exists | +| 2 | Install Tailwind CSS 4 | `pnpm add tailwindcss @tailwindcss/vite` | vite.config.ts updated | +| 3 | Configure vite.config.ts | Add Tailwind plugin + path aliases | Build succeeds | +| 4 | Configure TypeScript | Add path aliases to both tsconfig files | Imports resolve | +| 5 | Update index.css | Replace content with Tailwind import + theme | Styles apply | +| 6 | Initialize shadcn/ui | `npx shadcn@latest init` | components.json created | +| 7 | Install shadcn components | Batch install all 21 components | All files in ui/ | +| 8 | Install additional deps | TanStack Query, TanStack Table, date-fns, lucide | package.json updated | +| 9 | Create .env.example | Environment variable template | File exists | +| 10 | Verify build | `pnpm build` | dist/ created | + +--- + +### Task 1: Create Vite Project + +```bash +# From project root (ForecastLabAI/) +cd /home/w7-shellsnake/w7-DEV_X1/w7-ForecastLabAI +pnpm create vite frontend --template react-ts +cd frontend +pnpm install +``` + +**Expected Output**: `frontend/` directory with React 19 + TypeScript template + +--- + +### Task 2: Install Tailwind CSS 4 + +```bash +cd frontend +pnpm add tailwindcss @tailwindcss/vite +pnpm add -D @types/node +``` + +--- + +### Task 3: Configure vite.config.ts + +Replace `frontend/vite.config.ts`: + +```typescript +import path from "path" +import tailwindcss from "@tailwindcss/vite" +import react from "@vitejs/plugin-react" +import { defineConfig } from "vite" + +export default defineConfig({ + plugins: [react(), tailwindcss()], + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), + }, + }, + server: { + port: 5173, + proxy: { + // Proxy API requests to backend during development + "/api": { + target: "http://localhost:8123", + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, ""), + }, + }, + }, +}) +``` + +--- + +### Task 4: Configure TypeScript Path Aliases + +Update `frontend/tsconfig.json`: + +```json +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ], + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + } +} +``` + +Update `frontend/tsconfig.app.json` (add to existing compilerOptions): + +```json +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src"] +} +``` + +--- + +### Task 5: Update index.css + +Replace `frontend/src/index.css`: + +```css +@import "tailwindcss"; + +/* shadcn/ui chart color variables */ +@layer base { + :root { + --chart-1: 221.2 83.2% 53.3%; + --chart-2: 142.1 76.2% 36.3%; + --chart-3: 47.9 95.8% 53.1%; + --chart-4: 24.6 95% 53.1%; + --chart-5: 280.1 93.6% 53.1%; + } + + .dark { + --chart-1: 217.2 91.2% 59.8%; + --chart-2: 142.1 70.6% 45.3%; + --chart-3: 47.9 95.8% 53.1%; + --chart-4: 24.6 95% 53.1%; + --chart-5: 280.1 93.6% 53.1%; + } +} +``` + +--- + +### Task 6: Initialize shadcn/ui + +```bash +cd frontend +npx shadcn@latest init +``` + +**When prompted, select:** +- Style: **New York** +- Base color: **Neutral** +- CSS variables for theming: **Yes** + +This creates `components.json` and updates `src/lib/utils.ts`. + +--- + +### Task 7: Install shadcn Components + +Install all required components in batches: + +```bash +cd frontend + +# Layout & Navigation (6 components) +npx shadcn@latest add card tabs navigation-menu sheet scroll-area separator + +# Data Display (5 components) +npx shadcn@latest add table badge skeleton pagination progress + +# Form Components (7 components) +npx shadcn@latest add button input select textarea calendar popover checkbox + +# Feedback & Overlays (4 components) +npx shadcn@latest add sonner tooltip alert-dialog dialog + +# Interactive (3 components) +npx shadcn@latest add collapsible accordion dropdown-menu + +# Charts (1 component - installs recharts automatically) +npx shadcn@latest add chart +``` + +**Total: 26 component installations** (some have sub-components) + +--- + +### Task 8: Install Additional Dependencies + +```bash +cd frontend + +# TanStack libraries for data tables and data fetching +pnpm add @tanstack/react-query @tanstack/react-table + +# Date utilities (used by calendar, date pickers) +pnpm add date-fns + +# Icons (used throughout UI) +pnpm add lucide-react + +# React Router for navigation +pnpm add react-router-dom +``` + +--- + +### Task 9: Create Environment Configuration + +Create `frontend/.env.example`: + +```env +# API Configuration +VITE_API_BASE_URL=http://localhost:8123 +VITE_WS_URL=ws://localhost:8123/agents/stream + +# Feature Flags +VITE_ENABLE_AGENT_CHAT=true +VITE_ENABLE_ADMIN_PANEL=true + +# Visualization +VITE_DEFAULT_PAGE_SIZE=25 +VITE_MAX_CHART_POINTS=365 +``` + +Create `frontend/.gitignore` (add to existing): + +```gitignore +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +# Dependencies +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# Environment +.env +.env.local +.env.*.local +``` + +--- + +### Task 10: Verify Setup + +```bash +cd frontend + +# Development build +pnpm dev +# Expected: Server starts on http://localhost:5173 + +# Production build +pnpm build +# Expected: dist/ directory created with no errors + +# Type check +pnpm tsc --noEmit +# Expected: No TypeScript errors +``` + +--- + +## Validation Loop + +### Level 1: Directory Structure Verification + +```bash +cd frontend + +# Verify shadcn components installed +ls src/components/ui/ | wc -l +# Expected: 20+ files (varies by shadcn version) + +# Verify key files exist +test -f components.json && echo "✓ components.json" +test -f src/lib/utils.ts && echo "✓ utils.ts" +test -f vite.config.ts && echo "✓ vite.config.ts" +test -f .env.example && echo "✓ .env.example" +``` + +### Level 2: Build Validation + +```bash +cd frontend + +# Clean build +rm -rf node_modules dist +pnpm install +pnpm build + +# Expected output: +# vite v6.x.x building for production... +# ✓ X modules transformed. +# dist/index.html +# dist/assets/*.js +# dist/assets/*.css +``` + +### Level 3: Development Server Test + +```bash +cd frontend +pnpm dev + +# In browser: Open http://localhost:5173 +# Expected: Vite + React welcome page loads +# Check: No console errors in DevTools +# Check: Tailwind styles apply (if you add a class) +``` + +### Level 4: Import Path Test + +Create a quick test by editing `frontend/src/App.tsx`: + +```tsx +import { Button } from "@/components/ui/button" + +function App() { + return ( +
+ +
+ ) +} + +export default App +``` + +Verify: +1. No import errors in IDE +2. Button renders with styles +3. No console errors + +--- + +## Final Validation Checklist + +- [ ] `frontend/` directory exists at project root +- [ ] `pnpm dev` starts without errors +- [ ] `pnpm build` completes without TypeScript errors +- [ ] `components.json` exists with New York style +- [ ] `src/components/ui/` contains 20+ component files +- [ ] `src/lib/utils.ts` contains `cn()` function +- [ ] `vite.config.ts` has Tailwind plugin and path aliases +- [ ] `tsconfig.json` and `tsconfig.app.json` have `@/*` path alias +- [ ] `src/index.css` has `@import "tailwindcss"` and chart variables +- [ ] `.env.example` contains all required variables +- [ ] TanStack Query, TanStack Table, date-fns, lucide-react installed +- [ ] React Router installed + +--- + +## Integration Points + +```yaml +BACKEND_CONNECTION: + - Frontend connects to: http://localhost:8123 (VITE_API_BASE_URL) + - WebSocket connects to: ws://localhost:8123/agents/stream (VITE_WS_URL) + - Vite proxy configured for /api/* -> localhost:8123 + +NEXT_PHASES: + - PRP-11B: Architecture - App shell, routing, layout components + - PRP-11C: Pages - Dashboard, Explorer, Visualize, Chat, Admin pages + +GIT: + - frontend/ is a NEW directory (not modifying existing files) + - .gitignore already has node_modules entry +``` + +--- + +## Anti-Patterns to Avoid + +- ❌ Don't use `npx shadcn-ui init` (deprecated package name) +- ❌ Don't use `@tailwind base;` syntax (v3) — use `@import "tailwindcss"` (v4) +- ❌ Don't use `process.env.VITE_*` — use `import.meta.env.VITE_*` +- ❌ Don't forget `@types/node` — needed for `path.resolve()` in vite.config.ts +- ❌ Don't add path aliases to only one tsconfig — need both `.json` and `.app.json` +- ❌ Don't run commands from project root without `cd frontend` +- ❌ Don't skip chart theme variables — needed for shadcn/ui chart component + +--- + +## Confidence Score Breakdown + +| Area | Score | Rationale | +|------|-------|-----------| +| Vite Scaffolding | 10/10 | `pnpm create vite` is battle-tested | +| Tailwind CSS 4 | 9/10 | New v4 syntax, well documented | +| shadcn/ui Init | 10/10 | CLI handles all complexity | +| Component Installation | 9/10 | Batch install is straightforward | +| TypeScript Config | 9/10 | Path aliases well documented | +| Environment Config | 10/10 | Simple file creation | +| **Overall** | **9.5/10** | Setup-only task with clear steps | + +--- + +## Notes for Implementing Agent + +1. **Execute commands sequentially** — each task depends on previous +2. **Run from project root** then `cd frontend` for npm commands +3. **Accept all shadcn defaults** or select New York style when prompted +4. **Verify each step** before proceeding to next +5. **No application code needed** — just scaffolding and config +6. **Test the build** as final validation diff --git a/PRPs/PRP-11B-dashboard-architecture.md b/PRPs/PRP-11B-dashboard-architecture.md new file mode 100644 index 00000000..8c96724f --- /dev/null +++ b/PRPs/PRP-11B-dashboard-architecture.md @@ -0,0 +1,1246 @@ +# PRP-11B: ForecastLab Dashboard — Architecture & Core Features + +**Feature**: INITIAL-11B.md — Architecture & Features Implementation +**Status**: Ready for Implementation +**Confidence Score**: 8.5/10 +**Prerequisites**: PRP-11A (Frontend Setup) COMPLETED + +--- + +## Goal + +Implement the core ForecastLab Dashboard architecture and features: + +1. **App Shell** with top navigation + route-level tabs (not sidebar) +2. **React Router** with protected routes and lazy loading +3. **API Client** with typed fetch wrapper and error handling +4. **TanStack Query** hooks for all backend endpoints +5. **Data Explorer** with reusable DataTable (server-side pagination) +6. **Time Series Charts** using shadcn/ui chart component +7. **Agent Chat Interface** with WebSocket streaming +8. **Admin Panel** for RAG sources and deployment aliases +9. **Dark/Light Theme** toggle with persistence + +This PRP builds on the scaffolded frontend from PRP-11A and implements the functional architecture. + +--- + +## Why + +- **User Experience**: Replace CLI with intuitive data exploration and visualization +- **Portfolio Demonstration**: Full-stack React 19 + FastAPI integration +- **Operational**: Admin panel for system management without raw API calls +- **Agent Interaction**: Chat interface for RAG queries and experiment orchestration + +--- + +## What + +### Route Structure + +| Route | Component | API Endpoint(s) | +|-------|-----------|-----------------| +| `/` | DashboardPage | `GET /analytics/kpis` | +| `/explorer/sales` | SalesExplorerPage | `GET /analytics/drilldowns` | +| `/explorer/stores` | StoresExplorerPage | `GET /dimensions/stores` | +| `/explorer/products` | ProductsExplorerPage | `GET /dimensions/products` | +| `/explorer/runs` | RunsExplorerPage | `GET /registry/runs` | +| `/explorer/jobs` | JobsMonitorPage | `GET /jobs` | +| `/visualize/forecast` | ForecastPage | Job results | +| `/visualize/backtest` | BacktestPage | Job results | +| `/chat` | ChatPage | `WS /agents/stream` | +| `/admin` | AdminPage | `GET /rag/sources`, `GET /registry/aliases` | + +### Success Criteria + +- [ ] App shell with NavigationMenu component and mobile Sheet drawer +- [ ] React Router configured with lazy-loaded routes +- [ ] API client with typed responses and RFC 7807 error handling +- [ ] TanStack Query hooks for all 10+ API endpoints +- [ ] DataTable component with server-side pagination (1-indexed) +- [ ] TimeSeriesChart component with actual/predicted lines +- [ ] WebSocket hook with reconnection and streaming support +- [ ] ChatMessage component with citations and tool call display +- [ ] Theme toggle with next-themes and localStorage persistence +- [ ] All TypeScript strict checks pass +- [ ] `pnpm build` succeeds without errors +- [ ] Responsive design (mobile-first) + +--- + +## All Needed Context + +### Documentation & References + +```yaml +# MUST READ - Critical Documentation +- url: https://ui.shadcn.com/docs/components/data-table + why: "Server-side DataTable pattern with TanStack Table" + critical: "Use manualPagination=true, pass pageCount, onPaginationChange" + +- url: https://tanstack.com/query/latest/docs/framework/react/guides/paginated-queries + why: "TanStack Query v5 pagination with placeholderData" + critical: "Use keepPreviousData function or placeholderData: (prev) => prev" + +- url: https://tanstack.com/query/latest/docs/framework/react/guides/migrating-to-v5 + why: "v5 migration - keepPreviousData → placeholderData" + critical: "Import keepPreviousData from @tanstack/react-query" + +- url: https://tanstack.com/table/latest/docs/guide/pagination + why: "Server-side pagination with manualPagination=true" + critical: "pageCount must be passed, use onPaginationChange callback" + +- url: https://ui.shadcn.com/docs/components/chart + why: "shadcn/ui chart wrapper for Recharts" + critical: "Uses ChartContainer, ChartTooltip, ChartConfig pattern" + +- url: https://tailwindcss.com/blog/tailwindcss-v4 + why: "Tailwind CSS 4 CSS-first configuration" + critical: "Use @import 'tailwindcss' NOT @tailwind directives" + +# Existing Codebase Patterns +- file: frontend/src/components/ui/table.tsx + why: "Existing table primitives - TableHead, TableRow, TableCell" + +- file: frontend/src/components/ui/chart.tsx + why: "Existing chart component with ChartContainer, ChartTooltip" + +- file: frontend/src/components/ui/button.tsx + why: "Button variants pattern with CVA" + +- file: frontend/vite.config.ts + why: "API proxy configured - /api/* → localhost:8123" +``` + +### Current Frontend Structure (After PRP-11A) + +``` +frontend/ +├── src/ +│ ├── components/ +│ │ └── ui/ # 26 shadcn/ui components installed +│ │ ├── button.tsx # CVA variants pattern +│ │ ├── card.tsx # Card, CardHeader, CardContent +│ │ ├── chart.tsx # ChartContainer, ChartTooltip +│ │ ├── navigation-menu.tsx # Top nav component +│ │ ├── sheet.tsx # Mobile drawer +│ │ ├── table.tsx # Table primitives +│ │ ├── tabs.tsx # Route-level tabs +│ │ └── ... (20+ more) +│ ├── lib/ +│ │ └── utils.ts # cn() utility +│ ├── App.tsx # Demo - needs replacement +│ ├── main.tsx # Entry point +│ └── index.css # Tailwind + theme vars +├── .env.example # VITE_API_BASE_URL, VITE_WS_URL +├── components.json # shadcn/ui config (New York style) +├── vite.config.ts # Tailwind plugin + /api proxy +└── package.json # All deps installed +``` + +### Desired Codebase Tree (Files to Create) + +``` +frontend/src/ +├── components/ +│ ├── ui/ # EXISTING - Don't modify +│ ├── layout/ # NEW: App shell +│ │ ├── app-shell.tsx # Main layout with nav +│ │ ├── top-nav.tsx # NavigationMenu + mobile Sheet +│ │ └── theme-toggle.tsx # Dark/light toggle button +│ ├── data-table/ # NEW: Reusable DataTable +│ │ ├── data-table.tsx # Main component +│ │ ├── data-table-pagination.tsx +│ │ └── data-table-toolbar.tsx # Filters row +│ ├── charts/ # NEW: Chart wrappers +│ │ ├── time-series-chart.tsx # Actual vs Predicted +│ │ ├── kpi-card.tsx # Dashboard KPI display +│ │ └── backtest-folds-chart.tsx +│ ├── chat/ # NEW: Agent chat +│ │ ├── chat-message.tsx # Message bubble with citations +│ │ ├── chat-input.tsx # Text input with send +│ │ └── tool-call-display.tsx # Collapsible tool calls +│ └── common/ # NEW: Shared components +│ ├── status-badge.tsx # SUCCESS/FAILED/PENDING badges +│ ├── date-range-picker.tsx # Calendar popover +│ └── error-display.tsx # Error state component +├── hooks/ # NEW: TanStack Query hooks +│ ├── use-stores.ts +│ ├── use-products.ts +│ ├── use-kpis.ts +│ ├── use-drilldowns.ts +│ ├── use-runs.ts +│ ├── use-aliases.ts +│ ├── use-jobs.ts +│ ├── use-rag-sources.ts +│ └── use-websocket.ts # WebSocket with reconnect +├── lib/ # EXTEND existing +│ ├── utils.ts # EXISTING - Keep as is +│ ├── api.ts # NEW: Typed fetch client +│ ├── query-client.ts # NEW: TanStack Query config +│ └── constants.ts # NEW: Route paths, etc. +├── pages/ # NEW: Route pages +│ ├── dashboard.tsx +│ ├── explorer/ +│ │ ├── sales.tsx +│ │ ├── stores.tsx +│ │ ├── products.tsx +│ │ ├── runs.tsx +│ │ └── jobs.tsx +│ ├── visualize/ +│ │ ├── forecast.tsx +│ │ └── backtest.tsx +│ ├── chat.tsx +│ └── admin.tsx +├── types/ # NEW: TypeScript types +│ ├── api.ts # Backend response types +│ └── index.ts # Re-exports +├── providers/ # NEW: Context providers +│ └── theme-provider.tsx # next-themes wrapper +├── App.tsx # REPLACE: Router + providers +└── main.tsx # KEEP: Entry point +``` + +### Backend API Summary (From Agent Exploration) + +```typescript +// ALL LIST ENDPOINTS: 1-indexed pagination +// Query: ?page=1&page_size=20 (max 100) +// Response: { items[], total, page, page_size } + +// Dimensions +GET /dimensions/stores → { stores[], total, page, page_size } +GET /dimensions/products → { products[], total, page, page_size } + +// Analytics +GET /analytics/kpis?start_date=&end_date= → { metrics: KPIMetrics, ... } +GET /analytics/drilldowns?dimension=store&start_date=&end_date= → { items[], ... } + +// Registry +GET /registry/runs → { runs[], total, page, page_size } +PATCH /registry/runs/{run_id} → RunResponse +GET /registry/compare/{a}/{b} → { run_a, run_b, config_diff, metrics_diff } +GET /registry/aliases → AliasResponse[] +POST /registry/aliases → AliasResponse +DELETE /registry/aliases/{name} → 204 + +// Jobs +GET /jobs → { jobs[], total, page, page_size } +POST /jobs → JobResponse (202 Accepted) +DELETE /jobs/{id} → 204 (cancel pending only) + +// RAG +GET /rag/sources → { sources[], total_sources, total_chunks } +POST /rag/index → IndexResponse (201) +DELETE /rag/sources/{id} → DeleteResponse + +// Agents +POST /agents/sessions → SessionResponse (201) +POST /agents/sessions/{id}/chat → ChatResponse +WebSocket /agents/stream → Streaming events +``` + +### Known Gotchas & Library Quirks + +```typescript +// CRITICAL: TanStack Query v5 - keepPreviousData migration +// OLD (v4): useQuery({ queryKey, queryFn, keepPreviousData: true }) +// NEW (v5): useQuery({ queryKey, queryFn, placeholderData: keepPreviousData }) +// Import: import { keepPreviousData } from '@tanstack/react-query' + +// CRITICAL: TanStack Table v8 - API changes +// Use: useReactTable() NOT useTable() +// Require: getCoreRowModel() import and usage +// Server-side: manualPagination: true, manualSorting: true + +// CRITICAL: Pagination is 1-indexed from backend +// Backend: page=1 is first page +// TanStack Table: pageIndex=0 is first page +// Convert: page = pageIndex + 1 + +// CRITICAL: Vite environment variables +// Access: import.meta.env.VITE_API_BASE_URL +// NOT: process.env.VITE_API_BASE_URL (Node.js only) +// Prefix: Must start with VITE_ to be exposed + +// CRITICAL: WebSocket reconnection +// Browser WebSocket has NO auto-reconnect +// Implement exponential backoff manually +// Use AbortController for cleanup + +// CRITICAL: next-themes with Vite +// Requires ThemeProvider with attribute="class" +// Add storageKey for localStorage persistence +// Wrap at App root level + +// CRITICAL: Decimal handling from backend +// Backend sends Decimal as string: "1234.56" +// Parse: parseFloat(value) +// Format: new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }) + +// CRITICAL: shadcn/ui chart colors +// Use CSS variables: var(--chart-1) through var(--chart-5) +// Already defined in index.css from PRP-11A +// ChartConfig maps dataKey to label and color +``` + +--- + +## Implementation Blueprint + +### Task Overview + +| # | Task | Description | Files Created | +|---|------|-------------|---------------| +| 1 | Create TypeScript types | Match backend schemas | `types/api.ts`, `types/index.ts` | +| 2 | Create API client | Typed fetch with error handling | `lib/api.ts` | +| 3 | Setup TanStack Query | QueryClient config | `lib/query-client.ts` | +| 4 | Create query hooks | All API endpoint hooks | `hooks/*.ts` | +| 5 | Create theme provider | next-themes wrapper | `providers/theme-provider.tsx` | +| 6 | Create app shell | Nav + layout | `components/layout/*.tsx` | +| 7 | Create DataTable | Reusable server-side table | `components/data-table/*.tsx` | +| 8 | Create common components | Badge, DatePicker, Error | `components/common/*.tsx` | +| 9 | Create chart components | TimeSeriesChart, KPICard | `components/charts/*.tsx` | +| 10 | Create chat components | Message, Input, ToolCall | `components/chat/*.tsx` | +| 11 | Create pages | All route pages | `pages/*.tsx` | +| 12 | Setup router | React Router + lazy load | `App.tsx` | +| 13 | Integration test | Verify with backend | Manual verification | + +--- + +### Task 1: Create TypeScript Types + +**File**: `frontend/src/types/api.ts` + +```typescript +// === Pagination === +export interface PaginatedResponse { + total: number + page: number + page_size: number +} + +// === Dimensions === +export interface Store { + id: number + code: string + name: string + region: string | null + city: string | null + store_type: string | null + created_at: string + updated_at: string +} + +export interface StoreListResponse extends PaginatedResponse { + stores: Store[] +} + +export interface Product { + id: number + sku: string + name: string + category: string | null + brand: string | null + base_price: string | null + base_cost: string | null + created_at: string + updated_at: string +} + +export interface ProductListResponse extends PaginatedResponse { + products: Product[] +} + +// === Analytics === +export interface KPIMetrics { + total_revenue: string + total_units: number + total_transactions: number + avg_unit_price: string | null + avg_basket_value: string | null +} + +export interface KPIResponse { + metrics: KPIMetrics + start_date: string + end_date: string + store_id: number | null + product_id: number | null + category: string | null +} + +export interface DrilldownItem { + dimension_value: string + dimension_id: number | null + metrics: KPIMetrics + rank: number + revenue_share_pct: string +} + +export type DrilldownDimension = 'store' | 'product' | 'category' | 'region' | 'date' + +export interface DrilldownResponse { + dimension: DrilldownDimension + items: DrilldownItem[] + total_items: number + start_date: string + end_date: string + store_id: number | null + product_id: number | null +} + +// === Registry === +export type RunStatus = 'pending' | 'running' | 'success' | 'failed' | 'archived' + +export interface ModelRun { + run_id: string + status: RunStatus + model_type: string + model_config: Record + feature_config: Record | null + config_hash: string + data_window_start: string + data_window_end: string + store_id: number + product_id: number + metrics: Record | null + artifact_uri: string | null + artifact_hash: string | null + artifact_size_bytes: number | null + runtime_info: Record | null + agent_context: Record | null + git_sha: string | null + error_message: string | null + started_at: string | null + completed_at: string | null + created_at: string + updated_at: string +} + +export interface RunListResponse extends PaginatedResponse { + runs: ModelRun[] +} + +export interface Alias { + alias_name: string + run_id: string + run_status: RunStatus + model_type: string + description: string | null + created_at: string + updated_at: string +} + +export interface RunCompareResponse { + run_a: ModelRun + run_b: ModelRun + config_diff: Record + metrics_diff: Record +} + +// === Jobs === +export type JobType = 'train' | 'predict' | 'backtest' +export type JobStatus = 'pending' | 'running' | 'completed' | 'failed' | 'cancelled' + +export interface Job { + job_id: string + job_type: JobType + status: JobStatus + params: Record + result: Record | null + error_message: string | null + error_type: string | null + run_id: string | null + started_at: string | null + completed_at: string | null + created_at: string + updated_at: string +} + +export interface JobListResponse extends PaginatedResponse { + jobs: Job[] +} + +export interface JobCreate { + job_type: JobType + params: Record +} + +// === RAG === +export interface RagSource { + source_id: string + source_type: string + source_path: string + chunk_count: number + content_hash: string + indexed_at: string + metadata: Record | null +} + +export interface SourceListResponse { + sources: RagSource[] + total_sources: number + total_chunks: number +} + +// === Agents WebSocket === +export type AgentEventType = + | 'text_delta' + | 'tool_call_start' + | 'tool_call_end' + | 'approval_required' + | 'complete' + | 'error' + +export interface AgentStreamEvent { + event_type: AgentEventType + data: Record + timestamp: string +} + +// === Error Response (RFC 7807) === +export interface ProblemDetail { + type: string + title: string + status: number + detail: string + instance?: string + errors?: Array<{ field: string; message: string; type: string }> + code?: string + request_id?: string +} +``` + +**File**: `frontend/src/types/index.ts` + +```typescript +export * from './api' +``` + +--- + +### Task 2: Create API Client + +**File**: `frontend/src/lib/api.ts` + +```typescript +import type { ProblemDetail } from '@/types/api' + +const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8123' + +interface RequestConfig { + method?: 'GET' | 'POST' | 'PATCH' | 'DELETE' + body?: unknown + params?: Record + signal?: AbortSignal +} + +export class ApiError extends Error { + constructor( + message: string, + public status: number, + public detail?: ProblemDetail + ) { + super(message) + this.name = 'ApiError' + } +} + +export async function api(endpoint: string, config: RequestConfig = {}): Promise { + const { method = 'GET', body, params, signal } = config + + const url = new URL(`${API_BASE_URL}${endpoint}`) + if (params) { + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined && value !== null && value !== '') { + url.searchParams.set(key, String(value)) + } + }) + } + + const response = await fetch(url.toString(), { + method, + headers: { + 'Content-Type': 'application/json', + }, + body: body ? JSON.stringify(body) : undefined, + signal, + }) + + // Handle 204 No Content + if (response.status === 204) { + return undefined as T + } + + const data = await response.json() + + if (!response.ok) { + const detail = data as ProblemDetail + throw new ApiError( + detail.detail || response.statusText, + response.status, + detail + ) + } + + return data as T +} + +// Helper for consistent error messages +export function getErrorMessage(error: unknown): string { + if (error instanceof ApiError) { + return error.detail?.detail || error.message + } + if (error instanceof Error) { + return error.message + } + return 'An unexpected error occurred' +} +``` + +--- + +### Task 3: Setup TanStack Query + +**File**: `frontend/src/lib/query-client.ts` + +```typescript +import { QueryClient } from '@tanstack/react-query' + +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 5 * 60 * 1000, // 5 minutes + gcTime: 10 * 60 * 1000, // 10 minutes (formerly cacheTime) + retry: 1, + refetchOnWindowFocus: false, + }, + mutations: { + retry: 0, + }, + }, +}) +``` + +--- + +### Task 4: Create Query Hooks + +**File**: `frontend/src/hooks/use-stores.ts` + +```typescript +import { useQuery, keepPreviousData } from '@tanstack/react-query' +import { api } from '@/lib/api' +import type { StoreListResponse } from '@/types/api' + +interface UseStoresParams { + page: number + pageSize: number + region?: string + storeType?: string + search?: string + enabled?: boolean +} + +export function useStores({ + page, + pageSize, + region, + storeType, + search, + enabled = true, +}: UseStoresParams) { + return useQuery({ + queryKey: ['stores', { page, pageSize, region, storeType, search }], + queryFn: () => + api('/dimensions/stores', { + params: { + page, + page_size: pageSize, + region, + store_type: storeType, + search: search && search.length >= 2 ? search : undefined, + }, + }), + placeholderData: keepPreviousData, + enabled, + }) +} + +export function useStore(storeId: number, enabled = true) { + return useQuery({ + queryKey: ['stores', storeId], + queryFn: () => api(`/dimensions/stores/${storeId}`), + enabled, + }) +} +``` + +**File**: `frontend/src/hooks/use-products.ts` + +```typescript +import { useQuery, keepPreviousData } from '@tanstack/react-query' +import { api } from '@/lib/api' +import type { ProductListResponse } from '@/types/api' + +interface UseProductsParams { + page: number + pageSize: number + category?: string + brand?: string + search?: string + enabled?: boolean +} + +export function useProducts({ + page, + pageSize, + category, + brand, + search, + enabled = true, +}: UseProductsParams) { + return useQuery({ + queryKey: ['products', { page, pageSize, category, brand, search }], + queryFn: () => + api('/dimensions/products', { + params: { + page, + page_size: pageSize, + category, + brand, + search: search && search.length >= 2 ? search : undefined, + }, + }), + placeholderData: keepPreviousData, + enabled, + }) +} +``` + +**File**: `frontend/src/hooks/use-kpis.ts` + +```typescript +import { useQuery } from '@tanstack/react-query' +import { api } from '@/lib/api' +import type { KPIResponse } from '@/types/api' + +interface UseKPIsParams { + startDate: string + endDate: string + storeId?: number + productId?: number + category?: string + enabled?: boolean +} + +export function useKPIs({ + startDate, + endDate, + storeId, + productId, + category, + enabled = true, +}: UseKPIsParams) { + return useQuery({ + queryKey: ['kpis', { startDate, endDate, storeId, productId, category }], + queryFn: () => + api('/analytics/kpis', { + params: { + start_date: startDate, + end_date: endDate, + store_id: storeId, + product_id: productId, + category, + }, + }), + enabled: enabled && !!startDate && !!endDate, + }) +} +``` + +**File**: `frontend/src/hooks/use-runs.ts` + +```typescript +import { useQuery, useMutation, useQueryClient, keepPreviousData } from '@tanstack/react-query' +import { api } from '@/lib/api' +import type { RunListResponse, ModelRun, Alias, RunCompareResponse } from '@/types/api' + +interface UseRunsParams { + page: number + pageSize: number + modelType?: string + status?: string + storeId?: number + productId?: number + enabled?: boolean +} + +export function useRuns({ + page, + pageSize, + modelType, + status, + storeId, + productId, + enabled = true, +}: UseRunsParams) { + return useQuery({ + queryKey: ['runs', { page, pageSize, modelType, status, storeId, productId }], + queryFn: () => + api('/registry/runs', { + params: { + page, + page_size: pageSize, + model_type: modelType, + status, + store_id: storeId, + product_id: productId, + }, + }), + placeholderData: keepPreviousData, + enabled, + }) +} + +export function useRun(runId: string, enabled = true) { + return useQuery({ + queryKey: ['runs', runId], + queryFn: () => api(`/registry/runs/${runId}`), + enabled: enabled && !!runId, + }) +} + +export function useCompareRuns(runIdA: string, runIdB: string, enabled = false) { + return useQuery({ + queryKey: ['runs', 'compare', runIdA, runIdB], + queryFn: () => api(`/registry/compare/${runIdA}/${runIdB}`), + enabled: enabled && !!runIdA && !!runIdB, + }) +} + +export function useAliases() { + return useQuery({ + queryKey: ['aliases'], + queryFn: () => api('/registry/aliases'), + }) +} + +export function useCreateAlias() { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: (data: { alias_name: string; run_id: string; description?: string }) => + api('/registry/aliases', { method: 'POST', body: data }), + onSuccess: () => { + void queryClient.invalidateQueries({ queryKey: ['aliases'] }) + }, + }) +} + +export function useDeleteAlias() { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: (aliasName: string) => + api(`/registry/aliases/${aliasName}`, { method: 'DELETE' }), + onSuccess: () => { + void queryClient.invalidateQueries({ queryKey: ['aliases'] }) + }, + }) +} +``` + +**File**: `frontend/src/hooks/use-jobs.ts` + +```typescript +import { useQuery, useMutation, useQueryClient, keepPreviousData } from '@tanstack/react-query' +import { api } from '@/lib/api' +import type { JobListResponse, Job, JobCreate } from '@/types/api' + +interface UseJobsParams { + page: number + pageSize: number + jobType?: string + status?: string + enabled?: boolean +} + +export function useJobs({ + page, + pageSize, + jobType, + status, + enabled = true, +}: UseJobsParams) { + return useQuery({ + queryKey: ['jobs', { page, pageSize, jobType, status }], + queryFn: () => + api('/jobs', { + params: { + page, + page_size: pageSize, + job_type: jobType, + status, + }, + }), + placeholderData: keepPreviousData, + refetchInterval: 5000, // Poll every 5 seconds + enabled, + }) +} + +export function useJob(jobId: string, enabled = true) { + return useQuery({ + queryKey: ['jobs', jobId], + queryFn: () => api(`/jobs/${jobId}`), + enabled: enabled && !!jobId, + refetchInterval: (query) => { + const status = query.state.data?.status + return status === 'pending' || status === 'running' ? 2000 : false + }, + }) +} + +export function useCreateJob() { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: (data: JobCreate) => + api('/jobs', { method: 'POST', body: data }), + onSuccess: () => { + void queryClient.invalidateQueries({ queryKey: ['jobs'] }) + }, + }) +} + +export function useCancelJob() { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: (jobId: string) => + api(`/jobs/${jobId}`, { method: 'DELETE' }), + onSuccess: () => { + void queryClient.invalidateQueries({ queryKey: ['jobs'] }) + }, + }) +} +``` + +**File**: `frontend/src/hooks/use-rag-sources.ts` + +```typescript +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' +import { api } from '@/lib/api' +import type { SourceListResponse } from '@/types/api' + +export function useRagSources() { + return useQuery({ + queryKey: ['rag-sources'], + queryFn: () => api('/rag/sources'), + }) +} + +export function useDeleteRagSource() { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: (sourceId: string) => + api(`/rag/sources/${sourceId}`, { method: 'DELETE' }), + onSuccess: () => { + void queryClient.invalidateQueries({ queryKey: ['rag-sources'] }) + }, + }) +} + +export function useIndexDocument() { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: (data: { source_type: string; source_path: string; content?: string }) => + api<{ source_id: string; chunks_created: number }>('/rag/index', { + method: 'POST', + body: data, + }), + onSuccess: () => { + void queryClient.invalidateQueries({ queryKey: ['rag-sources'] }) + }, + }) +} +``` + +**File**: `frontend/src/hooks/use-websocket.ts` + +```typescript +import { useEffect, useRef, useState, useCallback } from 'react' + +type ConnectionStatus = 'connecting' | 'connected' | 'disconnected' | 'error' + +interface UseWebSocketOptions { + onMessage?: (data: unknown) => void + onError?: (error: Event) => void + reconnectAttempts?: number + reconnectInterval?: number +} + +export function useWebSocket(url: string | null, options: UseWebSocketOptions = {}) { + const { + onMessage, + onError, + reconnectAttempts = 5, + reconnectInterval = 3000, + } = options + + const [status, setStatus] = useState('disconnected') + const wsRef = useRef(null) + const reconnectCountRef = useRef(0) + const reconnectTimeoutRef = useRef>() + + const connect = useCallback(() => { + if (!url) return + + // Clear any pending reconnect + if (reconnectTimeoutRef.current) { + clearTimeout(reconnectTimeoutRef.current) + } + + setStatus('connecting') + const ws = new WebSocket(url) + + ws.onopen = () => { + setStatus('connected') + reconnectCountRef.current = 0 + } + + ws.onmessage = (event) => { + try { + const data = JSON.parse(event.data as string) as unknown + onMessage?.(data) + } catch { + onMessage?.(event.data) + } + } + + ws.onerror = (error) => { + setStatus('error') + onError?.(error) + } + + ws.onclose = () => { + setStatus('disconnected') + wsRef.current = null + + // Attempt reconnection with exponential backoff + if (reconnectCountRef.current < reconnectAttempts) { + const delay = reconnectInterval * Math.pow(2, reconnectCountRef.current) + reconnectCountRef.current++ + reconnectTimeoutRef.current = setTimeout(connect, delay) + } + } + + wsRef.current = ws + }, [url, onMessage, onError, reconnectAttempts, reconnectInterval]) + + const disconnect = useCallback(() => { + reconnectCountRef.current = reconnectAttempts // Prevent auto-reconnect + if (reconnectTimeoutRef.current) { + clearTimeout(reconnectTimeoutRef.current) + } + wsRef.current?.close() + wsRef.current = null + setStatus('disconnected') + }, [reconnectAttempts]) + + const send = useCallback((data: unknown) => { + if (wsRef.current?.readyState === WebSocket.OPEN) { + wsRef.current.send(typeof data === 'string' ? data : JSON.stringify(data)) + return true + } + return false + }, []) + + useEffect(() => { + if (url) { + connect() + } + return () => { + if (reconnectTimeoutRef.current) { + clearTimeout(reconnectTimeoutRef.current) + } + wsRef.current?.close() + } + }, [url, connect]) + + return { status, send, disconnect, reconnect: connect } +} +``` + +--- + +### Task 5-12: Remaining Implementation + +Due to length constraints, the remaining tasks (5-12) follow the same patterns as above. The key components are: + +**Task 5: Theme Provider** - Use `next-themes` with `attribute="class"` and `storageKey="forecastlab-theme"` + +**Task 6: App Shell** - Use `NavigationMenu` for desktop, `Sheet` for mobile drawer + +**Task 7: DataTable** - Wrap TanStack Table with server-side pagination, use `manualPagination: true` + +**Task 8: Common Components** - StatusBadge (CVA variants), DateRangePicker (Calendar + Popover) + +**Task 9: Charts** - Use shadcn/ui `ChartContainer` with Recharts `LineChart` + +**Task 10: Chat Components** - ChatMessage with Collapsible tool calls, scroll-area for history + +**Task 11: Pages** - Implement each page following patterns from INITIAL-11C + +**Task 12: Router** - React Router v7 with lazy loading via `React.lazy()` + +--- + +## Validation Loop + +### Level 1: TypeScript Compilation + +```bash +cd frontend + +# TypeScript strict check +pnpm tsc --noEmit + +# Expected: No errors +``` + +### Level 2: Linting + +```bash +cd frontend + +# ESLint +pnpm lint + +# Expected: No errors (or only shadcn component warnings) +``` + +### Level 3: Build Validation + +```bash +cd frontend + +# Production build +pnpm build + +# Expected: +# ✓ dist/index.html created +# ✓ dist/assets/*.js created +# ✓ No TypeScript errors +``` + +### Level 4: Integration Test + +```bash +# Terminal 1: Start backend +docker-compose up -d +uv run uvicorn app.main:app --port 8123 + +# Terminal 2: Start frontend +cd frontend +pnpm dev + +# Manual verification: +# 1. Open http://localhost:5173 +# 2. Navigate to /explorer/stores +# 3. Verify data loads (may need seeded data) +# 4. Test pagination (next/prev buttons) +# 5. Toggle dark mode +# 6. Test mobile view (resize browser) +# 7. Open browser DevTools - no console errors +``` + +--- + +## Final Validation Checklist + +- [ ] All TypeScript types match backend schemas +- [ ] API client handles errors with RFC 7807 format +- [ ] TanStack Query hooks use `keepPreviousData` for pagination +- [ ] DataTable converts 0-indexed pageIndex to 1-indexed page +- [ ] WebSocket hook implements exponential backoff reconnection +- [ ] Theme toggle persists to localStorage +- [ ] NavigationMenu renders on desktop +- [ ] Sheet drawer works on mobile +- [ ] Charts use ChartContainer with CSS variable colors +- [ ] All pages render without errors +- [ ] `pnpm build` succeeds +- [ ] `pnpm tsc --noEmit` passes + +--- + +## Integration Points + +```yaml +BACKEND_DEPENDENCY: + - Backend must be running on http://localhost:8123 + - Vite proxy configured: /api/* → localhost:8123 + - WebSocket: ws://localhost:8123/agents/stream + +ENVIRONMENT_VARIABLES: + - VITE_API_BASE_URL: Backend URL (default: http://localhost:8123) + - VITE_WS_URL: WebSocket URL (default: ws://localhost:8123/agents/stream) + - VITE_ENABLE_AGENT_CHAT: Feature flag for chat (default: true) + - VITE_ENABLE_ADMIN_PANEL: Feature flag for admin (default: true) + +SEEDED_DATA: + - Stores: GET /dimensions/stores should return data + - Products: GET /dimensions/products should return data + - Sales: GET /analytics/drilldowns should return data (if seeded) +``` + +--- + +## Anti-Patterns to Avoid + +- ❌ Don't use `keepPreviousData: true` — use `placeholderData: keepPreviousData` +- ❌ Don't use `useTable()` — use `useReactTable()` (TanStack Table v8) +- ❌ Don't forget `manualPagination: true` for server-side tables +- ❌ Don't use `process.env` — use `import.meta.env` for Vite +- ❌ Don't hardcode API URLs — always use environment variables +- ❌ Don't create WebSocket without cleanup — use useEffect return function +- ❌ Don't forget page conversion — backend is 1-indexed, TanStack is 0-indexed +- ❌ Don't use `@tailwind` directives — use `@import "tailwindcss"` (v4) + +--- + +## Confidence Score Breakdown + +| Area | Score | Rationale | +|------|-------|-----------| +| TypeScript Types | 9/10 | Backend schemas well-documented in routes.py | +| API Client | 9/10 | Simple fetch wrapper, error handling documented | +| TanStack Query | 9/10 | v5 migration well-documented, hooks straightforward | +| TanStack Table | 8/10 | Server-side pattern documented, 1-index conversion needed | +| shadcn/ui Components | 9/10 | Already installed, patterns in existing code | +| Charts | 8/10 | ChartContainer pattern exists in chart.tsx | +| WebSocket | 7/10 | Custom implementation, reconnection logic needed | +| Theme Toggle | 9/10 | next-themes well-documented for Vite | +| **Overall** | **8.5/10** | All foundational pieces in place | + +--- + +## Notes for Implementing Agent + +1. **Execute tasks in order** — types and API client must come first +2. **Don't modify existing ui/ components** — only create new files +3. **Test incrementally** — run `pnpm tsc --noEmit` after each task +4. **Use existing patterns** — button.tsx shows CVA variant pattern +5. **Convert pagination** — page = pageIndex + 1 for all API calls +6. **Import from @/** — use configured path aliases +7. **Check console for errors** — API errors should show ApiError details + +--- + +## Documentation Sources + +- [shadcn/ui Data Table](https://ui.shadcn.com/docs/components/data-table) +- [TanStack Query v5 Pagination](https://tanstack.com/query/latest/docs/framework/react/guides/paginated-queries) +- [TanStack Query v5 Migration](https://tanstack.com/query/latest/docs/framework/react/guides/migrating-to-v5) +- [TanStack Table Server-Side Pagination](https://tanstack.com/table/latest/docs/guide/pagination) +- [shadcn/ui Charts](https://ui.shadcn.com/docs/components/chart) +- [Tailwind CSS v4](https://tailwindcss.com/blog/tailwindcss-v4) +- [tablecn - Server-side shadcn table](https://github.com/sadmann7/tablecn) diff --git a/PRPs/PRP-11C-validation.md b/PRPs/PRP-11C-validation.md new file mode 100644 index 00000000..5c24693c --- /dev/null +++ b/PRPs/PRP-11C-validation.md @@ -0,0 +1,538 @@ +# PRP-11C: ForecastLab Dashboard — Validation & Testing + +**Feature**: INITIAL-11C.md — Pages & Components Validation +**Status**: Ready for Validation +**Confidence Score**: 9/10 +**Prerequisites**: PRP-11A (Setup) ✅ COMPLETED, PRP-11B (Architecture) ✅ COMPLETED + +--- + +## Goal + +Validate and test the existing ForecastLab Dashboard implementation against INITIAL-11C.md specifications: + +1. **Verify all routes render correctly** with no console errors +2. **Test server-side pagination** on all DataTable pages +3. **Validate chart components** render with mock data +4. **Test WebSocket chat** connection and message flow +5. **Verify responsive design** on mobile viewports +6. **Run type checking and linting** to ensure code quality +7. **Test dark/light theme** toggle and persistence +8. **Validate accessibility** (keyboard navigation, screen reader support) + +--- + +## Why + +- **Quality Assurance**: Confirm implementation matches INITIAL-11C specifications +- **Regression Prevention**: Establish baseline tests for future changes +- **Integration Verification**: Ensure frontend correctly integrates with backend API +- **User Experience**: Validate responsive design and accessibility + +--- + +## What + +### Implementation Status + +All pages and components from INITIAL-11C.md have been implemented: + +| Route | Status | Component | +|-------|--------|-----------| +| `/` | ✅ | DashboardPage with KPIs | +| `/explorer/sales` | ✅ | Sales drilldowns with tabs | +| `/explorer/stores` | ✅ | Store DataTable | +| `/explorer/products` | ✅ | Product DataTable | +| `/explorer/runs` | ✅ | Model runs DataTable | +| `/explorer/jobs` | ✅ | Jobs monitor | +| `/visualize/forecast` | ✅ | Forecast TimeSeriesChart | +| `/visualize/backtest` | ✅ | Backtest folds chart | +| `/chat` | ✅ | Agent chat with WebSocket | +| `/admin` | ✅ | RAG sources & aliases | + +### Components Implemented + +| Component | Location | Status | +|-----------|----------|--------| +| DataTable | `components/data-table/data-table.tsx` | ✅ | +| DataTableToolbar | `components/data-table/data-table-toolbar.tsx` | ✅ | +| DataTablePagination | `components/data-table/data-table-pagination.tsx` | ✅ | +| TimeSeriesChart | `components/charts/time-series-chart.tsx` | ✅ | +| BacktestFoldsChart | `components/charts/backtest-folds-chart.tsx` | ✅ | +| KPICard | `components/charts/kpi-card.tsx` | ✅ | +| ChatMessage | `components/chat/chat-message.tsx` | ✅ | +| ChatInput | `components/chat/chat-input.tsx` | ✅ | +| ToolCallDisplay | `components/chat/tool-call-display.tsx` | ✅ | +| DateRangePicker | `components/common/date-range-picker.tsx` | ✅ | +| StatusBadge | `components/common/status-badge.tsx` | ✅ | +| ErrorDisplay | `components/common/error-display.tsx` | ✅ | +| LoadingState | `components/common/loading-state.tsx` | ✅ | +| AppShell | `components/layout/app-shell.tsx` | ✅ | +| TopNav | `components/layout/top-nav.tsx` | ✅ | +| ThemeToggle | `components/layout/theme-toggle.tsx` | ✅ | + +### Success Criteria + +- [ ] `pnpm build` succeeds with no errors +- [ ] `pnpm lint` passes with no errors +- [ ] `pnpm tsc --noEmit` passes with no type errors +- [ ] All 10 routes render without console errors +- [ ] DataTable pagination works (page navigation, page size) +- [ ] Filters work on Model Runs page (status, model type) +- [ ] DateRangePicker updates Dashboard and Sales explorer +- [ ] TimeSeriesChart renders with sample data +- [ ] BacktestFoldsChart renders fold metrics +- [ ] Chat page establishes WebSocket connection +- [ ] Theme toggle persists across page refresh +- [ ] Mobile navigation sheet opens and closes +- [ ] All interactive elements are keyboard accessible + +--- + +## All Needed Context + +### Documentation & References + +```yaml +# Validation Resources +- file: frontend/README.md + why: "Complete documentation of implemented features" + +- file: frontend/src/App.tsx + why: "Route configuration and lazy loading setup" + +- file: frontend/src/lib/constants.ts + why: "Route paths and navigation items" + +- file: frontend/src/types/api.ts + why: "API response types for validation" + +# INITIAL-11C Specification +- file: INITIAL-11C.md + why: "Original specification for validation checklist" + +# Backend API for Integration Testing +- url: http://localhost:8123/docs + why: "API documentation for endpoint verification" +``` + +### Current Codebase Tree (Frontend) + +```bash +frontend/ +├── src/ +│ ├── components/ +│ │ ├── charts/ # KPICard, TimeSeriesChart, BacktestFoldsChart +│ │ ├── chat/ # ChatMessage, ChatInput, ToolCallDisplay +│ │ ├── common/ # DateRangePicker, StatusBadge, ErrorDisplay +│ │ ├── data-table/ # DataTable, DataTableToolbar, Pagination +│ │ ├── layout/ # AppShell, TopNav, ThemeToggle +│ │ └── ui/ # 26 shadcn/ui components +│ ├── hooks/ # use-stores, use-products, use-kpis, etc. +│ ├── lib/ # api.ts, constants.ts, utils.ts +│ ├── pages/ +│ │ ├── explorer/ # stores, products, runs, jobs, sales +│ │ ├── visualize/ # forecast, backtest +│ │ ├── dashboard.tsx +│ │ ├── chat.tsx +│ │ └── admin.tsx +│ ├── providers/ # theme-provider.tsx +│ └── types/ # api.ts (TypeScript types) +├── components.json # shadcn/ui config +├── package.json +└── vite.config.ts +``` + +### Known Gotchas & Library Quirks + +```typescript +// CRITICAL: API pagination is 1-indexed +// Frontend uses 0-indexed pagination state, must convert: +// page: pagination.pageIndex + 1 (API request) +// pagination.pageIndex = page - 1 (from API response) + +// CRITICAL: TanStack Query v5 uses keepPreviousData function +import { keepPreviousData } from '@tanstack/react-query' +// NOT: import { keepPreviousData } from 'react-query' (v3) + +// CRITICAL: Tailwind CSS 4 uses @import not @tailwind +// In index.css: @import 'tailwindcss'; +// NOT: @tailwind base; @tailwind components; etc. + +// CRITICAL: WebSocket URL must match backend +// Default: ws://localhost:8123/agents/stream +// Set via: VITE_WS_URL environment variable + +// CRITICAL: shadcn/ui chart requires ChartConfig +// Always define chartConfig with colors matching CSS variables +``` + +--- + +## Implementation Blueprint + +### Task 1: Build & Type Validation + +Verify the codebase compiles without errors. + +```bash +# Run in frontend/ directory +cd frontend + +# Step 1: Clean install dependencies +rm -rf node_modules pnpm-lock.yaml +pnpm install + +# Step 2: Type check (strict mode) +pnpm tsc --noEmit + +# Step 3: Lint check +pnpm lint + +# Step 4: Production build +pnpm build + +# Expected: All commands succeed with exit code 0 +``` + +### Task 2: Route Rendering Validation + +Manually verify each route renders correctly. + +```bash +# Start dev server +pnpm dev + +# Open browser to http://localhost:5173 +# Navigate to each route and check: +# 1. No console errors +# 2. Page content renders +# 3. Loading states show during data fetch +# 4. Error states show for API failures + +# Test sequence: +# 1. / (Dashboard) - KPI cards, top stores/products +# 2. /explorer/sales - Tabs for dimension switching +# 3. /explorer/stores - DataTable with pagination +# 4. /explorer/products - DataTable with search +# 5. /explorer/runs - DataTable with filters +# 6. /explorer/jobs - Job monitor table +# 7. /visualize/forecast - Job ID input, chart +# 8. /visualize/backtest - Job ID input, fold chart +# 9. /chat - Session creation, message input +# 10. /admin - RAG sources, aliases tabs +``` + +### Task 3: DataTable Pagination Testing + +Verify server-side pagination on DataTable pages. + +```typescript +// Test on /explorer/runs page: + +// 1. Verify page indicator shows "Page 1 of X" +// 2. Click "Next" - verify page changes +// 3. Click "Previous" - verify returns to page 1 +// 4. Change page size dropdown - verify data refreshes +// 5. Apply filter - verify pagination resets to page 1 +// 6. Click "Reset" - verify filters clear + +// Expected behavior: +// - Page size default: 25 +// - Pagination controls disabled when on first/last page +// - Loading skeleton shows during page transitions +``` + +### Task 4: Chart Component Testing + +Verify chart components render correctly. + +```typescript +// Test TimeSeriesChart (/visualize/forecast): +// 1. Enter a valid job ID +// 2. Verify chart renders with predicted line +// 3. Hover over data points - verify tooltip +// 4. Check legend shows "Predicted" + +// Test BacktestFoldsChart (/visualize/backtest): +// 1. Enter a valid backtest job ID +// 2. Verify bar chart renders for MAE +// 3. Switch to sMAPE tab - verify chart updates +// 4. Switch to WAPE, Bias tabs +// 5. Verify MetricsSummary displays aggregated values + +// Test KPICard (Dashboard): +// 1. Verify 4 KPI cards render +// 2. Verify loading skeleton during fetch +// 3. Verify values update on date range change +``` + +### Task 5: WebSocket Chat Testing + +Verify chat interface and WebSocket connection. + +```typescript +// Prerequisites: Backend must be running with agents endpoint + +// Test sequence: +// 1. Navigate to /chat +// 2. Select agent type (RAG Assistant) +// 3. Click "Start Session" - verify WebSocket connects +// 4. Status shows "Connected" in green +// 5. Type message, press Enter or click Send +// 6. User message appears in chat +// 7. Streaming response appears with typing indicator +// 8. Final message shows with citations (if any) +// 9. Click "New Session" - verify session resets + +// Error cases: +// - Backend not running: should show "disconnected" status +// - Network error: should show reconnection attempts +``` + +### Task 6: Theme Toggle Testing + +Verify dark/light mode toggle. + +```typescript +// Test sequence: +// 1. Click theme toggle button (top-right) +// 2. Verify theme changes (light → dark) +// 3. Refresh page - verify theme persists +// 4. Click toggle again - verify returns to light +// 5. Check localStorage for "theme" key + +// Visual checks: +// - Background color changes +// - Text color remains readable +// - Chart colors adjust for dark mode +// - Borders and cards adapt +``` + +### Task 7: Responsive Design Testing + +Verify mobile responsiveness. + +```typescript +// Using browser DevTools: +// 1. Resize to mobile width (375px) +// 2. Verify navigation collapses to hamburger menu +// 3. Click hamburger - Sheet opens from left +// 4. Navigate via Sheet - verify routes work +// 5. Click outside Sheet - verify it closes +// 6. Verify DataTable is scrollable horizontally +// 7. Verify charts resize appropriately +// 8. Verify DateRangePicker is usable on mobile +``` + +### Task 8: Accessibility Testing + +Verify keyboard navigation and screen reader support. + +```typescript +// Keyboard navigation: +// 1. Tab through navigation items +// 2. Enter to select, Escape to close dropdowns +// 3. Tab through DataTable rows +// 4. Use arrow keys in Select dropdowns +// 5. Enter to submit chat message + +// Screen reader: +// 1. Buttons have aria-labels +// 2. Form inputs have associated labels +// 3. Status badges have appropriate text +// 4. Charts have accessibility layer (accessibilityLayer prop) +``` + +--- + +## Validation Loop + +### Level 1: Build Validation + +```bash +cd frontend + +# Clean and reinstall +rm -rf node_modules dist +pnpm install + +# Type check +pnpm tsc --noEmit +# Expected: No errors + +# Lint +pnpm lint +# Expected: No errors + +# Build +pnpm build +# Expected: Build succeeds, outputs to dist/ +``` + +### Level 2: Visual Regression Testing + +```bash +# Start dev server +pnpm dev + +# Run through each route manually +# Document any visual issues or console errors + +# Optional: Screenshot each page for comparison +# Use browser DevTools > Capture screenshot +``` + +### Level 3: Integration Testing (with Backend) + +```bash +# Terminal 1: Start backend +cd /home/w7-shellsnake/w7-DEV_X1/w7-ForecastLabAI +docker-compose up -d +uv run uvicorn app.main:app --reload --port 8123 + +# Terminal 2: Start frontend +cd frontend +pnpm dev + +# Test API integration: +# 1. Dashboard loads KPIs from /analytics/kpis +# 2. Explorer pages fetch paginated data +# 3. Admin page loads RAG sources +# 4. Chat connects to WebSocket + +# Verify in Network tab: +# - API requests use correct endpoints +# - Responses are parsed correctly +# - Error responses show ErrorDisplay +``` + +### Level 4: Cross-Browser Testing + +```bash +# Test in multiple browsers: +# - Chrome (primary) +# - Firefox +# - Safari (if available) +# - Edge + +# Check for: +# - CSS rendering differences +# - WebSocket compatibility +# - LocalStorage persistence +``` + +--- + +## Final Validation Checklist + +### Build & Types +- [ ] `pnpm install` completes without errors +- [ ] `pnpm tsc --noEmit` passes +- [ ] `pnpm lint` passes +- [ ] `pnpm build` succeeds + +### Routes +- [ ] `/` (Dashboard) renders KPI cards +- [ ] `/explorer/sales` renders tabs and drilldowns +- [ ] `/explorer/stores` renders DataTable with pagination +- [ ] `/explorer/products` renders DataTable with search +- [ ] `/explorer/runs` renders DataTable with filters +- [ ] `/explorer/jobs` renders job monitor +- [ ] `/visualize/forecast` renders TimeSeriesChart +- [ ] `/visualize/backtest` renders BacktestFoldsChart +- [ ] `/chat` renders and connects WebSocket +- [ ] `/admin` renders RAG sources and aliases + +### Components +- [ ] DataTable shows loading skeleton +- [ ] DataTable pagination changes pages +- [ ] DataTableToolbar filters work +- [ ] TimeSeriesChart renders with legends +- [ ] BacktestFoldsChart switches metrics +- [ ] ChatMessage shows citations +- [ ] DateRangePicker updates data +- [ ] StatusBadge shows correct variants +- [ ] ErrorDisplay shows on API errors +- [ ] LoadingState shows during fetches + +### UX +- [ ] Theme toggle works (light ↔ dark) +- [ ] Theme persists on refresh +- [ ] Mobile navigation works +- [ ] Keyboard navigation works +- [ ] No console errors in production build + +### API Integration (with backend running) +- [ ] Dashboard fetches KPIs +- [ ] Explorer pages paginate correctly +- [ ] Filters send correct query params +- [ ] WebSocket connects and streams +- [ ] Error responses display correctly + +--- + +## Anti-Patterns to Avoid + +- ❌ Don't skip build verification before testing +- ❌ Don't test only happy paths - test error states +- ❌ Don't ignore console warnings or errors +- ❌ Don't test only on desktop - verify mobile +- ❌ Don't skip keyboard navigation testing +- ❌ Don't assume backend is running - test offline state + +--- + +## Validation Report Template + +After completing validation, fill in this report: + +```markdown +## Validation Report - INITIAL-11C + +**Date**: YYYY-MM-DD +**Tester**: [Name] +**Build Version**: [git commit hash] + +### Build Status +- Type Check: ✅ PASS / ❌ FAIL +- Lint: ✅ PASS / ❌ FAIL +- Build: ✅ PASS / ❌ FAIL + +### Route Status +| Route | Renders | No Errors | Notes | +|-------|---------|-----------|-------| +| / | ✅/❌ | ✅/❌ | | +| /explorer/sales | ✅/❌ | ✅/❌ | | +| ... | | | | + +### Component Status +| Component | Works | Notes | +|-----------|-------|-------| +| DataTable | ✅/❌ | | +| TimeSeriesChart | ✅/❌ | | +| ... | | | + +### Issues Found +1. [Description] - [Severity: Critical/High/Medium/Low] +2. ... + +### Recommendations +1. [Action item] +2. ... + +### Overall Status: ✅ PASS / ❌ NEEDS FIXES +``` + +--- + +## Summary + +This PRP provides a comprehensive validation framework for the INITIAL-11C implementation. All components and pages have been implemented. The validation tasks focus on: + +1. **Build Integrity** - TypeScript, ESLint, production build +2. **Functional Testing** - Each route and component +3. **Integration Testing** - API and WebSocket connectivity +4. **UX Testing** - Responsiveness, accessibility, theming + +Run through each task sequentially, documenting results in the validation report template. Any issues found should be logged as GitHub issues or fixed before marking validation complete. diff --git a/README.md b/README.md index 22741d4a..45b02926 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Portfolio-grade end-to-end retail demand forecasting system. - **ForecastOps**: Model zoo with time-based backtesting (rolling/expanding splits) + metrics - **Serving Layer**: Typed FastAPI endpoints (Pydantic v2 validation) - **Model Registry**: Run configs, metrics, artifacts, and data windows for reproducibility +- **Dashboard**: React 19 + Vite + Tailwind CSS 4 + shadcn/ui for data exploration and model management - **RAG Knowledge Base**: Postgres pgvector embeddings + evidence-grounded answers with citations - **Agentic Layer**: PydanticAI agents for autonomous experimentation and evidence-grounded Q&A with human-in-the-loop approval @@ -16,6 +17,7 @@ Portfolio-grade end-to-end retail demand forecasting system. ### Prerequisites - Python 3.12+ +- Node.js 20+ and pnpm (for frontend) - Docker and Docker Compose - uv (recommended) or pip @@ -65,6 +67,24 @@ curl http://localhost:8123/health # Response: {"status":"ok"} ``` +### Frontend Setup + +8. **Install frontend dependencies** + +```bash +cd frontend +pnpm install +``` + +9. **Start the development server** + +```bash +pnpm dev +# Frontend available at http://localhost:5173 +``` + +The frontend proxies API requests to the backend at `http://localhost:8123`. + ## Development ### Testing @@ -92,7 +112,7 @@ uv run pytest app/features/backtesting/tests/ -v -m integration # Backtesting i - Marked with `@pytest.mark.integration` - Require `docker-compose up -d` before running -### Commands +### Backend Commands ```bash # Type checking @@ -108,12 +128,33 @@ uv run alembic revision --autogenerate -m "description" uv run alembic upgrade head ``` +### Frontend Commands + +```bash +cd frontend + +# Development server (http://localhost:5173) +pnpm dev + +# Production build +pnpm build + +# Linting +pnpm lint + +# Type checking +pnpm tsc --noEmit + +# Preview production build +pnpm preview +``` + ### Project Structure ``` -app/ -├── core/ # Config, database, logging, middleware, exceptions -├── shared/ # Pagination, timestamps, error schemas +app/ # FastAPI backend +├── core/ # Config, database, logging, middleware, exceptions +├── shared/ # Pagination, timestamps, error schemas ├── features/ │ ├── data_platform/ # Store, product, calendar, sales tables │ ├── ingest/ # Batch upsert endpoints for sales data @@ -126,19 +167,29 @@ app/ │ ├── dimensions/ # Store/product discovery for LLM tool-calling │ ├── analytics/ # KPI aggregations and drilldown analysis │ └── jobs/ # Async-ready task orchestration -└── main.py # FastAPI entry point - -tests/ # Test fixtures and helpers -alembic/ # Database migrations +└── main.py # FastAPI entry point + +frontend/ # React dashboard (Vite + shadcn/ui) +├── src/ +│ ├── components/ui/ # shadcn/ui components (26 components) +│ ├── lib/ # Utilities (cn helper) +│ ├── App.tsx # Main app component +│ └── main.tsx # Entry point +├── components.json # shadcn/ui configuration +├── vite.config.ts # Vite + Tailwind + path aliases +└── package.json # Dependencies + +tests/ # Test fixtures and helpers +alembic/ # Database migrations examples/ -├── api/ # HTTP client examples -├── schema/ # Table documentation -├── queries/ # Example SQL queries -├── models/ # Baseline model examples (naive, seasonal_naive, moving_average) -├── backtest/ # Backtesting examples (run_backtest, inspect_splits, metrics_demo) +├── api/ # HTTP client examples +├── schema/ # Table documentation +├── queries/ # Example SQL queries +├── models/ # Baseline model examples (naive, seasonal_naive, moving_average) +├── backtest/ # Backtesting examples (run_backtest, inspect_splits, metrics_demo) ├── compute_features_demo.py # Feature engineering demo -└── registry_demo.py # Model registry workflow demo -scripts/ # Utility scripts +└── registry_demo.py # Model registry workflow demo +scripts/ # Utility scripts ``` ### Database Schema @@ -613,11 +664,32 @@ All error responses follow RFC 7807 Problem Details format with `Content-Type: a ## API Documentation -Once the server is running: +Once the backend is running: - Swagger UI: http://localhost:8123/docs - ReDoc: http://localhost:8123/redoc +## Frontend Stack + +The dashboard is built with modern React tooling: + +| Technology | Version | Purpose | +|------------|---------|---------| +| React | 19 | UI framework | +| Vite | 7 | Build tool and dev server | +| TypeScript | 5.9 | Type safety | +| Tailwind CSS | 4 | Utility-first styling | +| shadcn/ui | New York | Component library (26 components) | +| TanStack Query | 5 | Server state management | +| TanStack Table | 8 | Data tables | +| React Router | 7 | Client-side routing | +| Recharts | 2 | Charts and visualizations | + +**Development URLs:** +- Frontend: http://localhost:5173 +- Backend API: http://localhost:8123 +- API Docs: http://localhost:8123/docs + ## License MIT diff --git a/app/main.py b/app/main.py index 2d721823..052da077 100644 --- a/app/main.py +++ b/app/main.py @@ -4,6 +4,7 @@ from contextlib import asynccontextmanager from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware from app.core.config import get_settings from app.core.exceptions import register_exception_handlers @@ -70,6 +71,26 @@ def create_app() -> FastAPI: ) # Middleware (order matters - first added = outermost) + # CORS middleware - allow frontend to access API + app.add_middleware( + CORSMiddleware, + allow_origins=[ + "http://localhost:5173", # Vite dev server (default) + "http://localhost:5174", # Vite dev server (alternate port) + "http://localhost:5175", # Vite dev server (alternate port) + "http://127.0.0.1:5173", + "http://127.0.0.1:5174", + "http://127.0.0.1:5175", + "http://10.0.0.121:5173", # LAN access + "http://10.0.0.121:5174", + "http://10.0.0.121:5175", + ] + if settings.is_development + else [], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], + ) app.add_middleware(RequestIdMiddleware) # Exception handlers diff --git a/docs/ADR/ADR-0002-frontend-architecture-vite-spa-first.md b/docs/ADR/ADR-0002-frontend-architecture-vite-spa-first.md index c4d37051..e3449ad4 100644 --- a/docs/ADR/ADR-0002-frontend-architecture-vite-spa-first.md +++ b/docs/ADR/ADR-0002-frontend-architecture-vite-spa-first.md @@ -1,7 +1,8 @@ # ADR-0002: Frontend Architecture — Vite SPA First -- Status: Accepted +- Status: Implemented - Date: 2026-01-26 +- Updated: 2026-02-01 ## Context ForecastLabAI needs a modern dashboard to showcase data exploration, model runs, training/prediction actions, and RAG Q&A. @@ -39,6 +40,21 @@ Use **Vite** to build a **React SPA dashboard** for Phase-0/Phase-1. - Keep API contracts stable and well-typed (OpenAPI + Pydantic schemas). - Keep UI logic thin; avoid embedding business rules in the frontend. +## Implementation + +The frontend was scaffolded in `frontend/` with: + +- **React 19** + TypeScript 5.9 (strict mode) +- **Vite 7** with `@tailwindcss/vite` plugin +- **Tailwind CSS 4** (new `@import "tailwindcss"` syntax) +- **shadcn/ui** (New York style) with 26 pre-installed components +- **TanStack Query** + **TanStack Table** for data management +- **React Router 7** for client-side routing +- **Recharts** for data visualization + +Path aliases (`@/`) configured in both `tsconfig.json` and `vite.config.ts`. +API proxy configured to forward `/api/*` requests to `http://localhost:8123`. + ## Links -- INITIAL: `INITIAL-8.md` (Dashboard), `INITIAL-7.md` (FastAPI Contracts) -- PRP: (to be created) `docs/PRP/PRP-frontend-dashboard.md` +- INITIAL: `INITIAL-11A.md` (Frontend Setup), `INITIAL-11B.md` (Architecture), `INITIAL-11C.md` (Pages) +- PRP: `PRPs/PRP-11A-frontend-setup.md` diff --git a/docs/PHASE-index.md b/docs/PHASE-index.md index f068bd7a..7ea39b91 100644 --- a/docs/PHASE-index.md +++ b/docs/PHASE-index.md @@ -18,7 +18,7 @@ This document indexes all implementation phases of the ForecastLabAI project. | 7 | Serving Layer | Completed | PRP-8 | [7-SERVING_LAYER.md](./PHASE/7-SERVING_LAYER.md) | | 8 | RAG Knowledge Base | Completed | PRP-9 | [8-RAG_KNOWLEDGE_BASE.md](./PHASE/8-RAG_KNOWLEDGE_BASE.md) | | 9 | Agentic Layer | Completed | PRP-10 | [9-AGENTIC_LAYER.md](./PHASE/9-AGENTIC_LAYER.md) | -| 10 | ForecastLab Dashboard | Pending | PRP-11 | - | +| 10 | ForecastLab Dashboard | In Progress | PRP-11A/B/C | [10-DASHBOARD.md](./PHASE/10-DASHBOARD.md) | --- @@ -371,15 +371,26 @@ agent_enable_streaming: bool = True --- -## Pending Phases +## In Progress Phases + +### [Phase 10: ForecastLab Dashboard ("The Face")](./PHASE/10-DASHBOARD.md) -### Phase 10: ForecastLab Dashboard ("The Face") User interface, data visualization, and agent interaction. -- React 19 + Vite + shadcn/ui + Tailwind CSS 4 -- TanStack Table for server-side data grids -- TanStack Query for data fetching and caching + +| Sub-Phase | Description | Status | +|-----------|-------------|--------| +| 10A: Setup | Project scaffolding, Vite + React 19 + Tailwind CSS 4 + shadcn/ui | ✅ Completed | +| 10B: Architecture | App shell, routing, layout, state management | 🔲 Pending | +| 10C: Pages | Dashboard, Explorer, Visualize, Chat, Admin | 🔲 Pending | + +**Technology Stack**: +- React 19 + TypeScript 5.9 (strict mode) +- Vite 7 with Tailwind CSS 4 (@tailwindcss/vite plugin) +- shadcn/ui (New York style, 26 components) +- TanStack Query + TanStack Table for data management +- React Router 7 for navigation - Recharts for time series visualization -- Agent chat interface with streaming and citations +- WebSocket integration for agent streaming --- @@ -428,3 +439,4 @@ Each phase document (`docs/PHASE/X-PHASE_NAME.md`) contains: | 2026-02-01 | 7 | Serving Layer with RFC 7807, dimensions, analytics, and jobs completed | | 2026-02-01 | 8 | RAG Knowledge Base with pgvector and Ollama embedding provider completed | | 2026-02-01 | 9 | Agentic Layer with PydanticAI agents and human-in-the-loop approval completed | +| 2026-02-01 | 10A | Frontend scaffolding: Vite + React 19 + Tailwind CSS 4 + shadcn/ui completed | diff --git a/docs/PHASE/10-DASHBOARD.md b/docs/PHASE/10-DASHBOARD.md new file mode 100644 index 00000000..e6a3cc06 --- /dev/null +++ b/docs/PHASE/10-DASHBOARD.md @@ -0,0 +1,473 @@ +# Phase 10: Dashboard ("The Face") + +**Date Started**: 2026-02-01 +**PRP**: [PRP-11A-frontend-setup.md](../../PRPs/PRP-11A-frontend-setup.md) +**INITIAL**: INITIAL-11A.md (Setup), INITIAL-11B.md (Architecture), INITIAL-11C.md (Pages) +**ADR**: [ADR-0002-frontend-architecture-vite-spa-first.md](../ADR/ADR-0002-frontend-architecture-vite-spa-first.md) + +--- + +## Executive Summary + +Phase 10 implements the **Dashboard** - the "Face" of ForecastLabAI that provides a modern React-based user interface for data exploration, model management, and agent interaction. + +### Phase 10 Sub-Phases + +| Sub-Phase | Description | Status | +|-----------|-------------|--------| +| **10A: Setup** | Project scaffolding, dependencies, shadcn/ui | ✅ Completed | +| **10B: Architecture** | App shell, routing, layout, state management | ✅ Completed | +| **10C: Pages** | Dashboard, Explorer, Visualize, Chat, Admin | ✅ Completed | + +--- + +## Phase 10A: Frontend Setup (Completed) + +### Key Features + +1. **Modern React Stack** + - React 19 with TypeScript 5.9 (strict mode) + - Vite 7 for fast builds and hot module replacement + - Path aliases (`@/`) for clean imports + +2. **Tailwind CSS 4** + - New `@import "tailwindcss"` syntax + - `@tailwindcss/vite` plugin integration + - CSS variables for theming + +3. **shadcn/ui Component Library** + - New York style theme with neutral base color + - 26 pre-installed components + - Customizable via CSS variables + +4. **Data Management** + - TanStack Query for server state + - TanStack Table for data grids + - React Router 7 for navigation + +5. **Development Experience** + - API proxy to backend (`/api` → `localhost:8123`) + - ESLint with React Refresh support + - Hot module replacement + +### Technology Stack + +| Technology | Version | Purpose | +|------------|---------|---------| +| React | 19.2 | UI framework | +| Vite | 7.3 | Build tool and dev server | +| TypeScript | 5.9 | Type safety (strict mode) | +| Tailwind CSS | 4.1 | Utility-first styling | +| shadcn/ui | New York | Component library | +| TanStack Query | 5.90 | Server state management | +| TanStack Table | 8.21 | Data tables | +| React Router | 7.13 | Client-side routing | +| Recharts | 2.15 | Charts and visualizations | +| date-fns | 4.1 | Date utilities | +| lucide-react | 0.563 | Icons | + +--- + +## Deliverables (Phase 10A) + +### Directory Structure + +``` +frontend/ +├── public/ +│ └── vite.svg # Vite default asset +├── src/ +│ ├── components/ +│ │ └── ui/ # shadcn/ui components (26 files) +│ │ ├── accordion.tsx +│ │ ├── alert-dialog.tsx +│ │ ├── badge.tsx +│ │ ├── button.tsx +│ │ ├── calendar.tsx +│ │ ├── card.tsx +│ │ ├── chart.tsx +│ │ ├── checkbox.tsx +│ │ ├── collapsible.tsx +│ │ ├── dialog.tsx +│ │ ├── dropdown-menu.tsx +│ │ ├── input.tsx +│ │ ├── navigation-menu.tsx +│ │ ├── pagination.tsx +│ │ ├── popover.tsx +│ │ ├── progress.tsx +│ │ ├── scroll-area.tsx +│ │ ├── select.tsx +│ │ ├── separator.tsx +│ │ ├── sheet.tsx +│ │ ├── skeleton.tsx +│ │ ├── sonner.tsx +│ │ ├── table.tsx +│ │ ├── tabs.tsx +│ │ ├── textarea.tsx +│ │ └── tooltip.tsx +│ ├── lib/ +│ │ └── utils.ts # cn() utility for class merging +│ ├── App.tsx # Main app component +│ ├── main.tsx # Entry point +│ └── index.css # Tailwind + shadcn theme variables +├── .env.example # Environment template +├── .gitignore # Node.js ignores +├── components.json # shadcn/ui configuration +├── eslint.config.js # ESLint flat config +├── index.html # HTML entry point +├── package.json # Dependencies +├── pnpm-lock.yaml # Lock file +├── tsconfig.json # TypeScript config (root) +├── tsconfig.app.json # App TypeScript config +├── tsconfig.node.json # Node TypeScript config +└── vite.config.ts # Vite + Tailwind + aliases +``` + +### Configuration Files + +#### vite.config.ts + +```typescript +import path from "path" +import tailwindcss from "@tailwindcss/vite" +import react from "@vitejs/plugin-react" +import { defineConfig } from "vite" + +export default defineConfig({ + plugins: [react(), tailwindcss()], + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), + }, + }, + server: { + port: 5173, + proxy: { + "/api": { + target: "http://localhost:8123", + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, ""), + }, + }, + }, +}) +``` + +#### components.json + +```json +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/index.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + } +} +``` + +#### .env.example + +```env +# API Configuration +VITE_API_BASE_URL=http://localhost:8123 +VITE_WS_URL=ws://localhost:8123/agents/stream + +# Feature Flags +VITE_ENABLE_AGENT_CHAT=true +VITE_ENABLE_ADMIN_PANEL=true + +# Visualization +VITE_DEFAULT_PAGE_SIZE=25 +VITE_MAX_CHART_POINTS=365 +``` + +--- + +## Commands + +```bash +cd frontend + +# Install dependencies +pnpm install + +# Development server (http://localhost:5173) +pnpm dev + +# Production build +pnpm build + +# Linting +pnpm lint + +# Type checking +pnpm tsc --noEmit + +# Preview production build +pnpm preview +``` + +--- + +## Validation Results (Phase 10A) + +### Build Verification + +```bash +pnpm build +``` + +**Result**: ✅ Build successful + +``` +vite v7.3.1 building client environment for production... +✓ 36 modules transformed. +dist/index.html 0.46 kB │ gzip: 0.29 kB +dist/assets/index-DZWA8ABU.css 66.09 kB │ gzip: 10.87 kB +dist/assets/index-CBp0D9dw.js 224.06 kB │ gzip: 70.70 kB +✓ built in 1.32s +``` + +### TypeScript Check + +```bash +pnpm tsc --noEmit +``` + +**Result**: ✅ 0 errors + +### ESLint Check + +```bash +pnpm lint +``` + +**Result**: ✅ No errors (shadcn/ui components excluded from react-refresh rule) + +### Component Verification + +```bash +ls src/components/ui/ | wc -l +``` + +**Result**: ✅ 26 component files + +--- + +## Phase 10B: Architecture (Completed) + +**Completion Date**: 2026-02-02 +**PRP**: [PRP-11B-dashboard-architecture.md](../../PRPs/PRP-11B-dashboard-architecture.md) + +### Deliverables + +1. **App Shell** + - Top navigation bar with logo, nav links, and theme toggle + - Mobile-responsive drawer navigation + - Outlet-based content area with consistent layout + +2. **Routing** + - React Router 7 with lazy loading for all pages + - Code-split chunks for optimal loading + - Nested routes for Explorer and Visualize sections + +3. **State Management** + - TanStack Query for server state with optimized caching + - Theme context with system preference detection + - URL-based pagination state + +4. **API Client** + - Typed fetch wrapper (`src/lib/api.ts`) + - RFC 7807 Problem Details error handling + - `ApiError` class for structured error handling + +5. **TanStack Query Hooks** + - `useStores`, `useProducts` - Dimension data + - `useKPIs`, `useDrilldowns` - Analytics data + - `useRuns`, `useJobs` - Model operations + - `useRagSources` - RAG management + - `useWebSocket` - Agent streaming with reconnection + +6. **Reusable Components** + - `DataTable` - Server-side pagination with TanStack Table v8 + - `DataTableToolbar` - Filters and search + - `StatusBadge` - Status indicators with variants + - `DateRangePicker` - Date range selection + - `ErrorDisplay` - Error states with retry + - `LoadingState` - Loading indicators + - `KPICard` - Metric display cards + - `TimeSeriesChart` - Forecast visualizations + - `BacktestFoldsChart` - Backtest fold results + +### Directory Structure (Phase 10B) + +``` +src/ +├── components/ +│ ├── charts/ # Chart components +│ │ ├── kpi-card.tsx +│ │ ├── time-series-chart.tsx +│ │ ├── backtest-folds-chart.tsx +│ │ └── index.ts +│ ├── chat/ # Agent chat components +│ │ ├── chat-message.tsx +│ │ ├── chat-input.tsx +│ │ ├── tool-call-display.tsx +│ │ └── index.ts +│ ├── common/ # Shared components +│ │ ├── status-badge.tsx +│ │ ├── date-range-picker.tsx +│ │ ├── error-display.tsx +│ │ ├── loading-state.tsx +│ │ └── index.ts +│ ├── data-table/ # DataTable components +│ │ ├── data-table.tsx +│ │ ├── data-table-pagination.tsx +│ │ ├── data-table-toolbar.tsx +│ │ └── index.ts +│ └── layout/ # Layout components +│ ├── app-shell.tsx +│ ├── top-nav.tsx +│ ├── theme-toggle.tsx +│ └── index.ts +├── hooks/ # TanStack Query hooks +│ ├── use-stores.ts +│ ├── use-products.ts +│ ├── use-kpis.ts +│ ├── use-drilldowns.ts +│ ├── use-runs.ts +│ ├── use-jobs.ts +│ ├── use-rag-sources.ts +│ ├── use-websocket.ts +│ └── index.ts +├── lib/ +│ ├── api.ts # API client +│ ├── query-client.ts # TanStack Query config +│ ├── constants.ts # Routes and nav items +│ ├── date-utils.ts # Date helpers +│ └── status-utils.ts # Status mapping +├── providers/ +│ └── theme-provider.tsx # Theme context +└── types/ + ├── api.ts # API type definitions + └── index.ts +``` + +### Build Verification (Phase 10B) + +```bash +pnpm build +``` + +**Result**: ✅ Build successful + +``` +vite v7.3.1 building client environment for production... +✓ 3470 modules transformed. +dist/index.html 0.46 kB │ gzip: 0.29 kB +dist/assets/index-[hash].css 79.08 kB │ gzip: 13.16 kB +dist/assets/index-[hash].js 435.43 kB │ gzip: 137.51 kB +✓ built in 7.96s +``` + +### ESLint Check (Phase 10B) + +**Result**: ✅ 0 errors, 1 warning (expected TanStack Table warning) + +--- + +## Phase 10C: Pages (Completed) + +**Completion Date**: 2026-02-02 +**PRP**: [PRP-11B-dashboard-architecture.md](../../PRPs/PRP-11B-dashboard-architecture.md) + +### Implemented Pages + +| Page | Route | Description | +|------|-------|-------------| +| Dashboard | `/` | KPI cards, top stores/products by revenue | +| Stores | `/explorer/stores` | Store list with region filter | +| Products | `/explorer/products` | Product catalog with category filter | +| Model Runs | `/explorer/runs` | Run history with model/status filters | +| Jobs | `/explorer/jobs` | Job monitor with cancel action | +| Sales | `/explorer/sales` | Drilldowns by store/product/category/region/date | +| Forecast | `/visualize/forecast` | Time series forecast visualization | +| Backtest | `/visualize/backtest` | Backtest fold metrics and comparison | +| Chat | `/chat` | Agent conversation with tool call display | +| Admin | `/admin` | RAG sources and deployment alias management | + +### Page Features + +- **Dashboard**: 4 KPI cards, top 5 stores, top 5 products with date range filter +- **Explorer Pages**: Server-side pagination, column filters, reset functionality +- **Jobs Page**: Cancel pending jobs with confirmation dialog +- **Sales Page**: Tab-based dimension switching (store/product/category/region/date) +- **Forecast Page**: Store/product selection, time series chart with actual vs predicted +- **Backtest Page**: Run selection, fold metrics chart, metrics summary card +- **Chat Page**: Message history, tool call visualization, WebSocket streaming +- **Admin Page**: RAG source table, deployment alias table with CRUD operations + +### Integration Points with Backend + +| Frontend Feature | Backend Endpoint | Status | +|------------------|------------------|--------| +| KPI Cards | `GET /analytics/kpis` | ✅ Ready | +| Data Tables | `GET /dimensions/*` | ✅ Ready | +| Forecast Charts | `POST /forecasting/predict` | ✅ Ready | +| Agent Chat | `WS /agents/stream` | ✅ Ready | +| Model Registry | `GET /registry/runs` | ✅ Ready | +| Backtest Results | `POST /backtesting/run` | ✅ Ready | + +--- + +## Dependencies on Previous Phases + +| Phase | Dependency | Status | +|-------|------------|--------| +| Phase 7 | Serving Layer (FastAPI) | ✅ Complete | +| Phase 8 | RAG Knowledge Base | ✅ Complete | +| Phase 9 | Agentic Layer (WebSocket) | ✅ Complete | + +--- + +## Known Limitations + +1. **No Tests**: Frontend testing setup deferred to future phase +2. **No Authentication**: Auth UI deferred to future phase +3. **Mock-Ready**: Pages render with mock data patterns; backend integration required + +--- + +## References + +- [Vite Documentation](https://vite.dev/guide/) +- [Tailwind CSS 4](https://tailwindcss.com/docs) +- [shadcn/ui Installation](https://ui.shadcn.com/docs/installation/vite) +- [TanStack Query](https://tanstack.com/query/latest) +- [TanStack Table](https://tanstack.com/table/latest) +- [React Router](https://reactrouter.com/) +- [ADR-0002](../ADR/ADR-0002-frontend-architecture-vite-spa-first.md) - Frontend Architecture Decision +- [PRP-11A](../../PRPs/PRP-11A-frontend-setup.md) - Frontend Setup PRP +- [PRP-11B](../../PRPs/PRP-11B-dashboard-architecture.md) - Dashboard Architecture PRP + +--- + +**Phase 10A Completion Date**: 2026-02-01 +**Phase 10B Completion Date**: 2026-02-02 +**Phase 10C Completion Date**: 2026-02-02 +**Phase Status**: ✅ Complete +**Files Changed**: 49 files, 4404 insertions diff --git a/frontend/.env.example b/frontend/.env.example new file mode 100644 index 00000000..e751d6fd --- /dev/null +++ b/frontend/.env.example @@ -0,0 +1,11 @@ +# API Configuration +VITE_API_BASE_URL=http://localhost:8123 +VITE_WS_URL=ws://localhost:8123/agents/stream + +# Feature Flags +VITE_ENABLE_AGENT_CHAT=true +VITE_ENABLE_ADMIN_PANEL=true + +# Visualization +VITE_DEFAULT_PAGE_SIZE=25 +VITE_MAX_CHART_POINTS=365 diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 00000000..2f030a23 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,29 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# Environment +.env +.env.local +.env.*.local diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 00000000..f358ec1b --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,256 @@ +# ForecastLabAI Frontend + +React-based dashboard for the ForecastLabAI retail demand forecasting system. + +## Tech Stack + +| Technology | Version | Purpose | +|------------|---------|---------| +| React | 19.2 | UI framework | +| Vite | 7.3 | Build tool and dev server | +| TypeScript | 5.9 | Type safety (strict mode) | +| Tailwind CSS | 4.1 | Utility-first styling | +| shadcn/ui | New York | Component library | +| TanStack Query | 5.90 | Server state management | +| TanStack Table | 8.21 | Data tables with pagination | +| React Router | 7.13 | Client-side routing | +| Recharts | 2.15 | Charts and visualizations | +| date-fns | 4.1 | Date utilities | + +## Quick Start + +```bash +# Install dependencies +pnpm install + +# Start development server (http://localhost:5173) +pnpm dev + +# Build for production +pnpm build + +# Lint code +pnpm lint + +# Type check +pnpm tsc --noEmit +``` + +## Project Structure + +``` +src/ +├── components/ +│ ├── ui/ # shadcn/ui primitives (26 components) +│ ├── charts/ # KPI cards, time series, backtest charts +│ ├── chat/ # Agent chat interface components +│ ├── common/ # StatusBadge, DateRangePicker, ErrorDisplay +│ ├── data-table/ # Server-side paginated DataTable +│ └── layout/ # AppShell, TopNav, ThemeToggle +├── hooks/ +│ ├── use-stores.ts # Store data hooks +│ ├── use-products.ts # Product data hooks +│ ├── use-kpis.ts # KPI metrics hooks +│ ├── use-drilldowns.ts # Sales drilldown hooks +│ ├── use-runs.ts # Model run hooks +│ ├── use-jobs.ts # Job monitoring hooks +│ ├── use-rag-sources.ts # RAG source hooks +│ └── use-websocket.ts # WebSocket connection hook +├── lib/ +│ ├── api.ts # Typed fetch wrapper with RFC 7807 errors +│ ├── query-client.ts # TanStack Query configuration +│ ├── constants.ts # Routes and navigation items +│ ├── date-utils.ts # Date range helpers +│ ├── status-utils.ts # Status variant mapping +│ └── utils.ts # cn() class merging utility +├── pages/ +│ ├── dashboard.tsx # Main dashboard with KPIs +│ ├── chat.tsx # Agent chat interface +│ ├── admin.tsx # RAG sources and deployment management +│ ├── explorer/ +│ │ ├── stores.tsx # Store browser +│ │ ├── products.tsx # Product browser +│ │ ├── runs.tsx # Model run explorer +│ │ ├── jobs.tsx # Job monitor +│ │ └── sales.tsx # Sales drilldown explorer +│ └── visualize/ +│ ├── forecast.tsx # Forecast visualization +│ └── backtest.tsx # Backtest results visualization +├── providers/ +│ └── theme-provider.tsx # Dark/light mode provider +├── types/ +│ └── api.ts # TypeScript types for all API responses +├── App.tsx # Router configuration with lazy loading +├── main.tsx # Entry point +└── index.css # Tailwind + shadcn theme variables +``` + +## Routes + +| Route | Page | Description | +|-------|------|-------------| +| `/` | Dashboard | KPI cards, top stores/products | +| `/explorer/stores` | Stores | Store list with pagination | +| `/explorer/products` | Products | Product catalog with filters | +| `/explorer/runs` | Model Runs | Model run history | +| `/explorer/jobs` | Jobs | Job queue monitoring | +| `/explorer/sales` | Sales | Sales drilldowns by dimension | +| `/visualize/forecast` | Forecast | Time series forecast charts | +| `/visualize/backtest` | Backtest | Backtest fold results | +| `/chat` | Chat | Agent conversation interface | +| `/admin` | Admin | RAG sources and deployments | + +## API Integration + +The frontend connects to the FastAPI backend at `http://localhost:8123`. In development, requests to `/api` are proxied to the backend. + +### API Client + +```typescript +import { api } from '@/lib/api' + +// Typed fetch with automatic error handling +const stores = await api.get>('/dimensions/stores') + +// RFC 7807 errors are thrown as ApiError +try { + await api.post('/registry/runs', payload) +} catch (error) { + if (error instanceof ApiError) { + console.log(error.type, error.title, error.detail) + } +} +``` + +### TanStack Query Hooks + +```typescript +import { useStores } from '@/hooks/use-stores' + +function StoreList() { + const { data, isLoading, error } = useStores({ + page: 1, + pageSize: 25, + region: 'West', + }) + // ... +} +``` + +## Component Patterns + +### DataTable with Server-Side Pagination + +```typescript +import { DataTable } from '@/components/data-table' +import { DataTableToolbar } from '@/components/data-table' + + + +``` + +### Status Badge + +```typescript +import { StatusBadge } from '@/components/common' +import { getStatusVariant } from '@/lib/status-utils' + + + {run.status} + +``` + +### Date Range Picker + +```typescript +import { DateRangePicker } from '@/components/common' + + +``` + +## Theming + +The app uses `next-themes` for dark/light mode with system preference detection. Theme CSS variables are defined in `src/index.css`. + +```typescript +import { ThemeToggle } from '@/components/layout' + +// Toggle button in navigation + +``` + +## Environment Variables + +Copy `.env.example` to `.env`: + +```env +# API Configuration +VITE_API_BASE_URL=http://localhost:8123 +VITE_WS_URL=ws://localhost:8123/agents/stream + +# Feature Flags +VITE_ENABLE_AGENT_CHAT=true +VITE_ENABLE_ADMIN_PANEL=true + +# Visualization +VITE_DEFAULT_PAGE_SIZE=25 +VITE_MAX_CHART_POINTS=365 +``` + +## Development + +### Adding shadcn/ui Components + +```bash +pnpm dlx shadcn@latest add [component-name] +``` + +### Adding New Pages + +1. Create page component in `src/pages/` +2. Add route to `src/App.tsx` +3. Add navigation item to `src/lib/constants.ts` + +### Adding New API Hooks + +1. Add TypeScript types to `src/types/api.ts` +2. Create hook in `src/hooks/use-[resource].ts` +3. Export from `src/hooks/index.ts` + +## Build Output + +Production build outputs to `dist/`: + +``` +dist/ +├── index.html 0.46 kB +├── assets/ +│ ├── index-[hash].css 79.08 kB (13.16 kB gzip) +│ └── index-[hash].js 435.43 kB (137.51 kB gzip) +``` + +Code splitting is enabled via React lazy loading, creating separate chunks for each page. + +## References + +- [Vite Documentation](https://vite.dev/guide/) +- [Tailwind CSS 4](https://tailwindcss.com/docs) +- [shadcn/ui](https://ui.shadcn.com/) +- [TanStack Query](https://tanstack.com/query/latest) +- [TanStack Table](https://tanstack.com/table/latest) +- [React Router](https://reactrouter.com/) diff --git a/frontend/components.json b/frontend/components.json new file mode 100644 index 00000000..c3085d6c --- /dev/null +++ b/frontend/components.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/index.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "rtl": false, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "registries": {} +} diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js new file mode 100644 index 00000000..8f1e12ab --- /dev/null +++ b/frontend/eslint.config.js @@ -0,0 +1,30 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + }, + // Disable react-refresh rule for shadcn/ui components (they export variants) + { + files: ['src/components/ui/**/*.{ts,tsx}'], + rules: { + 'react-refresh/only-export-components': 'off', + }, + }, +]) diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 00000000..072a57e8 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + frontend + + +
+ + + diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 00000000..8166e890 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,60 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@radix-ui/react-accordion": "^1.2.12", + "@radix-ui/react-alert-dialog": "^1.1.15", + "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-collapsible": "^1.1.12", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-navigation-menu": "^1.2.14", + "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-progress": "^1.1.8", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-tooltip": "^1.2.8", + "@tailwindcss/vite": "^4.1.18", + "@tanstack/react-query": "^5.90.20", + "@tanstack/react-table": "^8.21.3", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "date-fns": "^4.1.0", + "lucide-react": "^0.563.0", + "next-themes": "^0.4.6", + "react": "^19.2.0", + "react-day-picker": "^9.13.0", + "react-dom": "^19.2.0", + "react-router-dom": "^7.13.0", + "recharts": "2.15.4", + "sonner": "^2.0.7", + "tailwind-merge": "^3.4.0", + "tailwindcss": "^4.1.18" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@types/node": "^24.10.1", + "@types/react": "^19.2.5", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "tw-animate-css": "^1.4.0", + "typescript": "~5.9.3", + "typescript-eslint": "^8.46.4", + "vite": "^7.2.4" + } +} diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml new file mode 100644 index 00000000..b3b8c11b --- /dev/null +++ b/frontend/pnpm-lock.yaml @@ -0,0 +1,4059 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@radix-ui/react-accordion': + specifier: ^1.2.12 + version: 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-alert-dialog': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-checkbox': + specifier: ^1.3.3 + version: 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-collapsible': + specifier: ^1.1.12 + version: 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-dialog': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.16 + version: 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-navigation-menu': + specifier: ^1.2.14 + version: 1.2.14(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-popover': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-progress': + specifier: ^1.1.8 + version: 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-scroll-area': + specifier: ^1.2.10 + version: 1.2.10(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-select': + specifier: ^2.2.6 + version: 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-separator': + specifier: ^1.1.8 + version: 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': + specifier: ^1.2.4 + version: 1.2.4(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-tabs': + specifier: ^1.1.13 + version: 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-tooltip': + specifier: ^1.2.8 + version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@tailwindcss/vite': + specifier: ^4.1.18 + version: 4.1.18(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.30.2)) + '@tanstack/react-query': + specifier: ^5.90.20 + version: 5.90.20(react@19.2.4) + '@tanstack/react-table': + specifier: ^8.21.3 + version: 8.21.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + date-fns: + specifier: ^4.1.0 + version: 4.1.0 + lucide-react: + specifier: ^0.563.0 + version: 0.563.0(react@19.2.4) + next-themes: + specifier: ^0.4.6 + version: 0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: + specifier: ^19.2.0 + version: 19.2.4 + react-day-picker: + specifier: ^9.13.0 + version: 9.13.0(react@19.2.4) + react-dom: + specifier: ^19.2.0 + version: 19.2.4(react@19.2.4) + react-router-dom: + specifier: ^7.13.0 + version: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + recharts: + specifier: 2.15.4 + version: 2.15.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + sonner: + specifier: ^2.0.7 + version: 2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + tailwind-merge: + specifier: ^3.4.0 + version: 3.4.0 + tailwindcss: + specifier: ^4.1.18 + version: 4.1.18 + devDependencies: + '@eslint/js': + specifier: ^9.39.1 + version: 9.39.2 + '@types/node': + specifier: ^24.10.1 + version: 24.10.9 + '@types/react': + specifier: ^19.2.5 + version: 19.2.10 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.10) + '@vitejs/plugin-react': + specifier: ^5.1.1 + version: 5.1.2(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.30.2)) + eslint: + specifier: ^9.39.1 + version: 9.39.2(jiti@2.6.1) + eslint-plugin-react-hooks: + specifier: ^7.0.1 + version: 7.0.1(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-react-refresh: + specifier: ^0.4.24 + version: 0.4.26(eslint@9.39.2(jiti@2.6.1)) + globals: + specifier: ^16.5.0 + version: 16.5.0 + tw-animate-css: + specifier: ^1.4.0 + version: 1.4.0 + typescript: + specifier: ~5.9.3 + version: 5.9.3 + typescript-eslint: + specifier: ^8.46.4 + version: 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + vite: + specifier: ^7.2.4 + version: 7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.30.2) + +packages: + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.0': + resolution: {integrity: sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.6': + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.0': + resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@date-fns/tz@1.4.1': + resolution: {integrity: sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==} + + '@esbuild/aix-ppc64@0.27.2': + resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.2': + resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.2': + resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.2': + resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.2': + resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.2': + resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.2': + resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.2': + resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.2': + resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.2': + resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.2': + resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.2': + resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.2': + resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.2': + resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.2': + resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.2': + resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.2': + resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.2': + resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.2': + resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.2': + resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.2': + resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.2': + resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.2': + resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.2': + resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.2': + resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.2': + resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.1': + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.3': + resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.2': + resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@floating-ui/core@1.7.4': + resolution: {integrity: sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==} + + '@floating-ui/dom@1.7.5': + resolution: {integrity: sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==} + + '@floating-ui/react-dom@2.1.7': + resolution: {integrity: sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.10': + resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + 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==} + + '@radix-ui/number@1.1.1': + resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + + '@radix-ui/primitive@1.1.3': + resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + + '@radix-ui/react-accordion@1.2.12': + resolution: {integrity: sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-alert-dialog@1.1.15': + resolution: {integrity: sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-arrow@1.1.7': + resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-checkbox@1.3.3': + resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collapsible@1.1.12': + resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.3': + resolution: {integrity: sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.15': + resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.11': + resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-dropdown-menu@2.1.16': + resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.3': + resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-menu@2.1.16': + resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-navigation-menu@1.2.14': + resolution: {integrity: sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popover@1.1.15': + resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popper@1.2.8': + resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.5': + resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.4': + resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-progress@1.1.8': + resolution: {integrity: sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-roving-focus@1.1.11': + resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-scroll-area@1.2.10': + resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-select@2.2.6': + resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-separator@1.1.8': + resolution: {integrity: sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-slot@1.2.4': + resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-tabs@1.1.13': + resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tooltip@1.2.8': + resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-previous@1.1.1': + resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.1': + resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-visually-hidden@1.2.3': + resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/rect@1.1.1': + resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + + '@rolldown/pluginutils@1.0.0-beta.53': + resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==} + + '@rollup/rollup-android-arm-eabi@4.57.1': + resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.57.1': + resolution: {integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.57.1': + resolution: {integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.57.1': + resolution: {integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.57.1': + resolution: {integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.57.1': + resolution: {integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': + resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.57.1': + resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.57.1': + resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.57.1': + resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.57.1': + resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.57.1': + resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.57.1': + resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.57.1': + resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.57.1': + resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.57.1': + resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.57.1': + resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.57.1': + resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.57.1': + resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.57.1': + resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.57.1': + resolution: {integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.57.1': + resolution: {integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.57.1': + resolution: {integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.57.1': + resolution: {integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.57.1': + resolution: {integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==} + cpu: [x64] + os: [win32] + + '@tailwindcss/node@4.1.18': + resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==} + + '@tailwindcss/oxide-android-arm64@4.1.18': + resolution: {integrity: sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.18': + resolution: {integrity: sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.18': + resolution: {integrity: sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.18': + resolution: {integrity: sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': + resolution: {integrity: sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': + resolution: {integrity: sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.18': + resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.18': + resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.1.18': + resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.1.18': + resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': + resolution: {integrity: sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.18': + resolution: {integrity: sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.18': + resolution: {integrity: sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==} + engines: {node: '>= 10'} + + '@tailwindcss/vite@4.1.18': + resolution: {integrity: sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 + + '@tanstack/query-core@5.90.20': + resolution: {integrity: sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==} + + '@tanstack/react-query@5.90.20': + resolution: {integrity: sha512-vXBxa+qeyveVO7OA0jX1z+DeyCA4JKnThKv411jd5SORpBKgkcVnYKCiBgECvADvniBX7tobwBmg01qq9JmMJw==} + peerDependencies: + react: ^18 || ^19 + + '@tanstack/react-table@8.21.3': + resolution: {integrity: sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==} + engines: {node: '>=12'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + + '@tanstack/table-core@8.21.3': + resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==} + engines: {node: '>=12'} + + '@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/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@24.10.9': + resolution: {integrity: sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==} + + '@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.10': + resolution: {integrity: sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==} + + '@typescript-eslint/eslint-plugin@8.54.0': + resolution: {integrity: sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.54.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.54.0': + resolution: {integrity: sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.54.0': + resolution: {integrity: sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.54.0': + resolution: {integrity: sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.54.0': + resolution: {integrity: sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.54.0': + resolution: {integrity: sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.54.0': + resolution: {integrity: sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.54.0': + resolution: {integrity: sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.54.0': + resolution: {integrity: sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.54.0': + resolution: {integrity: sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitejs/plugin-react@5.1.2': + resolution: {integrity: sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + baseline-browser-mapping@2.9.19: + resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} + hasBin: true + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001766: + resolution: {integrity: sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + date-fns-jalali@4.1.0-0: + resolution: {integrity: sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==} + + date-fns@4.1.0: + resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js-light@2.5.1: + resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + + dom-helpers@5.2.1: + resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + + electron-to-chromium@1.5.283: + resolution: {integrity: sha512-3vifjt1HgrGW/h76UEeny+adYApveS9dH2h3p57JYzBSXJIKUJAvtmIytDKjcSCt9xHfrNCFJ7gts6vkhuq++w==} + + enhanced-resolve@5.18.4: + resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==} + engines: {node: '>=10.13.0'} + + esbuild@0.27.2: + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-react-hooks@7.0.1: + resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react-refresh@0.4.26: + resolution: {integrity: sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==} + peerDependencies: + eslint: '>=8.40' + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.39.2: + resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-equals@5.4.0: + resolution: {integrity: sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==} + engines: {node: '>=6.0.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + 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'} + + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@16.5.0: + resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} + engines: {node: '>=18'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: 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==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + 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==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-android-arm64@1.30.2: + resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.30.2: + resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.2: + resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.2: + resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.2: + resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.2: + resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.2: + resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.2: + resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.2: + resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.2: + resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.2: + resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.2: + resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} + engines: {node: '>= 12.0.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash@4.17.23: + resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lucide-react@0.563.0: + resolution: {integrity: sha512-8dXPB2GI4dI8jV4MgUDGBeLdGk8ekfqVZ0BdLcrRzocGgG75ltNEmWS+gE7uokKF/0oSUuczNDT+g9hFJ23FkA==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + next-themes@0.4.6: + resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} + peerDependencies: + react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + react-day-picker@9.13.0: + resolution: {integrity: sha512-euzj5Hlq+lOHqI53NiuNhCP8HWgsPf/bBAVijR50hNaY1XwjKjShAnIe8jm8RD2W9IJUvihDIZ+KrmqfFzNhFQ==} + engines: {node: '>=18'} + peerDependencies: + react: '>=16.8.0' + + react-dom@19.2.4: + resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} + peerDependencies: + react: ^19.2.4 + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react-refresh@0.18.0: + resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} + engines: {node: '>=0.10.0'} + + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-router-dom@7.13.0: + resolution: {integrity: sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + react-router@7.13.0: + resolution: {integrity: sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + + react-smooth@4.0.4: + resolution: {integrity: sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-transition-group@4.4.5: + resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} + peerDependencies: + react: '>=16.6.0' + react-dom: '>=16.6.0' + + react@19.2.4: + resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} + engines: {node: '>=0.10.0'} + + recharts-scale@0.4.5: + resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==} + + recharts@2.15.4: + resolution: {integrity: sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==} + engines: {node: '>=14'} + peerDependencies: + react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + rollup@4.57.1: + resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + sonner@2.0.7: + resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + tailwind-merge@3.4.0: + resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==} + + tailwindcss@4.1.18: + resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==} + + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: '>=6'} + + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + ts-api-utils@2.4.0: + resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tw-animate-css@1.4.0: + resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript-eslint@8.54.0: + resolution: {integrity: sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + 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==} + + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + victory-vendor@36.9.2: + resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==} + + vite@7.3.1: + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} + 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 + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + +snapshots: + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.0': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@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.0': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + + '@babel/parser@7.29.0': + dependencies: + '@babel/types': 7.29.0 + + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/runtime@7.28.6': {} + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.0 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@date-fns/tz@1.4.1': {} + + '@esbuild/aix-ppc64@0.27.2': + optional: true + + '@esbuild/android-arm64@0.27.2': + optional: true + + '@esbuild/android-arm@0.27.2': + optional: true + + '@esbuild/android-x64@0.27.2': + optional: true + + '@esbuild/darwin-arm64@0.27.2': + optional: true + + '@esbuild/darwin-x64@0.27.2': + optional: true + + '@esbuild/freebsd-arm64@0.27.2': + optional: true + + '@esbuild/freebsd-x64@0.27.2': + optional: true + + '@esbuild/linux-arm64@0.27.2': + optional: true + + '@esbuild/linux-arm@0.27.2': + optional: true + + '@esbuild/linux-ia32@0.27.2': + optional: true + + '@esbuild/linux-loong64@0.27.2': + optional: true + + '@esbuild/linux-mips64el@0.27.2': + optional: true + + '@esbuild/linux-ppc64@0.27.2': + optional: true + + '@esbuild/linux-riscv64@0.27.2': + optional: true + + '@esbuild/linux-s390x@0.27.2': + optional: true + + '@esbuild/linux-x64@0.27.2': + optional: true + + '@esbuild/netbsd-arm64@0.27.2': + optional: true + + '@esbuild/netbsd-x64@0.27.2': + optional: true + + '@esbuild/openbsd-arm64@0.27.2': + optional: true + + '@esbuild/openbsd-x64@0.27.2': + optional: true + + '@esbuild/openharmony-arm64@0.27.2': + optional: true + + '@esbuild/sunos-x64@0.27.2': + optional: true + + '@esbuild/win32-arm64@0.27.2': + optional: true + + '@esbuild/win32-ia32@0.27.2': + optional: true + + '@esbuild/win32-x64@0.27.2': + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2(jiti@2.6.1))': + dependencies: + eslint: 9.39.2(jiti@2.6.1) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.1': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.3': + dependencies: + ajv: 6.12.6 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.2': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@floating-ui/core@1.7.4': + dependencies: + '@floating-ui/utils': 0.2.10 + + '@floating-ui/dom@1.7.5': + dependencies: + '@floating-ui/core': 1.7.4 + '@floating-ui/utils': 0.2.10 + + '@floating-ui/react-dom@2.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@floating-ui/dom': 1.7.5 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@floating-ui/utils@0.2.10': {} + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@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 + + '@radix-ui/number@1.1.1': {} + + '@radix-ui/primitive@1.1.3': {} + + '@radix-ui/react-accordion@1.2.12(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + + '@radix-ui/react-alert-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.10)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + + '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.10)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + + '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.10)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.10)(react@19.2.4)': + dependencies: + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.10 + + '@radix-ui/react-context@1.1.2(@types/react@19.2.10)(react@19.2.4)': + dependencies: + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.10 + + '@radix-ui/react-context@1.1.3(@types/react@19.2.10)(react@19.2.4)': + dependencies: + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.10 + + '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.4) + aria-hidden: 1.2.6 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + react-remove-scroll: 2.7.2(@types/react@19.2.10)(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + + '@radix-ui/react-direction@1.1.1(@types/react@19.2.10)(react@19.2.4)': + dependencies: + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.10 + + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.10)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.10)(react@19.2.4)': + dependencies: + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.10 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + + '@radix-ui/react-id@1.1.1(@types/react@19.2.10)(react@19.2.4)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.4) + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.10 + + '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.4) + aria-hidden: 1.2.6 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + react-remove-scroll: 2.7.2(@types/react@19.2.10)(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + + '@radix-ui/react-navigation-menu@1.2.14(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + + '@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.4) + aria-hidden: 1.2.6 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + react-remove-scroll: 2.7.2(@types/react@19.2.10)(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@floating-ui/react-dom': 2.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/rect': 1.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.10)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + + '@radix-ui/react-primitive@2.1.4(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/react-slot': 1.2.4(@types/react@19.2.10)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + + '@radix-ui/react-progress@1.1.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/react-context': 1.1.3(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + + '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + + '@radix-ui/react-select@2.2.6(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + aria-hidden: 1.2.6 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + react-remove-scroll: 2.7.2(@types/react@19.2.10)(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + + '@radix-ui/react-separator@1.1.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + + '@radix-ui/react-slot@1.2.3(@types/react@19.2.10)(react@19.2.4)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.10 + + '@radix-ui/react-slot@1.2.4(@types/react@19.2.10)(react@19.2.4)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.10 + + '@radix-ui/react-tabs@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.10)(react@19.2.4)': + dependencies: + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.10 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.10)(react@19.2.4)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.4) + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.10 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.10)(react@19.2.4)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.4) + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.10 + + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.10)(react@19.2.4)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.4) + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.10 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.10)(react@19.2.4)': + dependencies: + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.10 + + '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.10)(react@19.2.4)': + dependencies: + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.10 + + '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.10)(react@19.2.4)': + dependencies: + '@radix-ui/rect': 1.1.1 + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.10 + + '@radix-ui/react-use-size@1.1.1(@types/react@19.2.10)(react@19.2.4)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.4) + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.10 + + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + + '@radix-ui/rect@1.1.1': {} + + '@rolldown/pluginutils@1.0.0-beta.53': {} + + '@rollup/rollup-android-arm-eabi@4.57.1': + optional: true + + '@rollup/rollup-android-arm64@4.57.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.57.1': + optional: true + + '@rollup/rollup-darwin-x64@4.57.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.57.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.57.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.57.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.57.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.57.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.57.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.57.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.57.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.57.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.57.1': + optional: true + + '@tailwindcss/node@4.1.18': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.18.4 + jiti: 2.6.1 + lightningcss: 1.30.2 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.1.18 + + '@tailwindcss/oxide-android-arm64@4.1.18': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.18': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.18': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.18': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.18': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.18': + optional: true + + '@tailwindcss/oxide@4.1.18': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.18 + '@tailwindcss/oxide-darwin-arm64': 4.1.18 + '@tailwindcss/oxide-darwin-x64': 4.1.18 + '@tailwindcss/oxide-freebsd-x64': 4.1.18 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.18 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.18 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.18 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.18 + '@tailwindcss/oxide-linux-x64-musl': 4.1.18 + '@tailwindcss/oxide-wasm32-wasi': 4.1.18 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.18 + + '@tailwindcss/vite@4.1.18(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.30.2))': + dependencies: + '@tailwindcss/node': 4.1.18 + '@tailwindcss/oxide': 4.1.18 + tailwindcss: 4.1.18 + vite: 7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.30.2) + + '@tanstack/query-core@5.90.20': {} + + '@tanstack/react-query@5.90.20(react@19.2.4)': + dependencies: + '@tanstack/query-core': 5.90.20 + react: 19.2.4 + + '@tanstack/react-table@8.21.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@tanstack/table-core': 8.21.3 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@tanstack/table-core@8.21.3': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + '@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.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/d3-array@3.2.2': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-shape@3.1.8': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/estree@1.0.8': {} + + '@types/json-schema@7.0.15': {} + + '@types/node@24.10.9': + dependencies: + undici-types: 7.16.0 + + '@types/react-dom@19.2.3(@types/react@19.2.10)': + dependencies: + '@types/react': 19.2.10 + + '@types/react@19.2.10': + dependencies: + csstype: 3.2.3 + + '@typescript-eslint/eslint-plugin@8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.54.0 + '@typescript-eslint/type-utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.54.0 + eslint: 9.39.2(jiti@2.6.1) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.54.0 + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.54.0 + debug: 4.4.3 + eslint: 9.39.2(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.54.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.54.0(typescript@5.9.3) + '@typescript-eslint/types': 8.54.0 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.54.0': + dependencies: + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/visitor-keys': 8.54.0 + + '@typescript-eslint/tsconfig-utils@8.54.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.2(jiti@2.6.1) + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.54.0': {} + + '@typescript-eslint/typescript-estree@8.54.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.54.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.54.0(typescript@5.9.3) + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/visitor-keys': 8.54.0 + debug: 4.4.3 + minimatch: 9.0.5 + semver: 7.7.3 + tinyglobby: 0.2.15 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.54.0 + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) + eslint: 9.39.2(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.54.0': + dependencies: + '@typescript-eslint/types': 8.54.0 + eslint-visitor-keys: 4.2.1 + + '@vitejs/plugin-react@5.1.2(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.30.2))': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) + '@rolldown/pluginutils': 1.0.0-beta.53 + '@types/babel__core': 7.20.5 + react-refresh: 0.18.0 + vite: 7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.30.2) + transitivePeerDependencies: + - supports-color + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + argparse@2.0.1: {} + + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + + balanced-match@1.0.2: {} + + baseline-browser-mapping@2.9.19: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.9.19 + caniuse-lite: 1.0.30001766 + electron-to-chromium: 1.5.283 + node-releases: 2.0.27 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001766: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + clsx@2.1.1: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + concat-map@0.0.1: {} + + convert-source-map@2.0.0: {} + + cookie@1.1.1: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + csstype@3.2.3: {} + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-color@3.1.0: {} + + d3-ease@3.0.1: {} + + d3-format@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@3.1.0: {} + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + date-fns-jalali@4.1.0-0: {} + + date-fns@4.1.0: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decimal.js-light@2.5.1: {} + + deep-is@0.1.4: {} + + detect-libc@2.1.2: {} + + detect-node-es@1.1.0: {} + + dom-helpers@5.2.1: + dependencies: + '@babel/runtime': 7.28.6 + csstype: 3.2.3 + + electron-to-chromium@1.5.283: {} + + enhanced-resolve@5.18.4: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 + + esbuild@0.27.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.2 + '@esbuild/android-arm': 0.27.2 + '@esbuild/android-arm64': 0.27.2 + '@esbuild/android-x64': 0.27.2 + '@esbuild/darwin-arm64': 0.27.2 + '@esbuild/darwin-x64': 0.27.2 + '@esbuild/freebsd-arm64': 0.27.2 + '@esbuild/freebsd-x64': 0.27.2 + '@esbuild/linux-arm': 0.27.2 + '@esbuild/linux-arm64': 0.27.2 + '@esbuild/linux-ia32': 0.27.2 + '@esbuild/linux-loong64': 0.27.2 + '@esbuild/linux-mips64el': 0.27.2 + '@esbuild/linux-ppc64': 0.27.2 + '@esbuild/linux-riscv64': 0.27.2 + '@esbuild/linux-s390x': 0.27.2 + '@esbuild/linux-x64': 0.27.2 + '@esbuild/netbsd-arm64': 0.27.2 + '@esbuild/netbsd-x64': 0.27.2 + '@esbuild/openbsd-arm64': 0.27.2 + '@esbuild/openbsd-x64': 0.27.2 + '@esbuild/openharmony-arm64': 0.27.2 + '@esbuild/sunos-x64': 0.27.2 + '@esbuild/win32-arm64': 0.27.2 + '@esbuild/win32-ia32': 0.27.2 + '@esbuild/win32-x64': 0.27.2 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-plugin-react-hooks@7.0.1(eslint@9.39.2(jiti@2.6.1)): + dependencies: + '@babel/core': 7.29.0 + '@babel/parser': 7.29.0 + eslint: 9.39.2(jiti@2.6.1) + hermes-parser: 0.25.1 + zod: 4.3.6 + zod-validation-error: 4.0.2(zod@4.3.6) + transitivePeerDependencies: + - supports-color + + eslint-plugin-react-refresh@0.4.26(eslint@9.39.2(jiti@2.6.1)): + dependencies: + eslint: 9.39.2(jiti@2.6.1) + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.39.2(jiti@2.6.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.1 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.3 + '@eslint/js': 9.39.2 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.6.1 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + eventemitter3@4.0.7: {} + + fast-deep-equal@3.1.3: {} + + fast-equals@5.4.0: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + get-nonce@1.0.1: {} + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@14.0.0: {} + + globals@16.5.0: {} + + graceful-fs@4.2.11: {} + + has-flag@4.0.0: {} + + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + internmap@2.0.3: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + isexe@2.0.0: {} + + jiti@2.6.1: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-android-arm64@1.30.2: + optional: true + + lightningcss-darwin-arm64@1.30.2: + optional: true + + lightningcss-darwin-x64@1.30.2: + optional: true + + lightningcss-freebsd-x64@1.30.2: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.2: + optional: true + + lightningcss-linux-arm64-gnu@1.30.2: + optional: true + + lightningcss-linux-arm64-musl@1.30.2: + optional: true + + lightningcss-linux-x64-gnu@1.30.2: + optional: true + + lightningcss-linux-x64-musl@1.30.2: + optional: true + + lightningcss-win32-arm64-msvc@1.30.2: + optional: true + + lightningcss-win32-x64-msvc@1.30.2: + optional: true + + lightningcss@1.30.2: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.30.2 + lightningcss-darwin-arm64: 1.30.2 + lightningcss-darwin-x64: 1.30.2 + lightningcss-freebsd-x64: 1.30.2 + lightningcss-linux-arm-gnueabihf: 1.30.2 + lightningcss-linux-arm64-gnu: 1.30.2 + lightningcss-linux-arm64-musl: 1.30.2 + lightningcss-linux-x64-gnu: 1.30.2 + lightningcss-linux-x64-musl: 1.30.2 + lightningcss-win32-arm64-msvc: 1.30.2 + lightningcss-win32-x64-msvc: 1.30.2 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + lodash@4.17.23: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lucide-react@0.563.0(react@19.2.4): + dependencies: + react: 19.2.4 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + natural-compare@1.4.0: {} + + next-themes@0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + node-releases@2.0.27: {} + + object-assign@4.1.1: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + picocolors@1.1.1: {} + + picomatch@4.0.3: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + punycode@2.3.1: {} + + react-day-picker@9.13.0(react@19.2.4): + dependencies: + '@date-fns/tz': 1.4.1 + date-fns: 4.1.0 + date-fns-jalali: 4.1.0-0 + react: 19.2.4 + + react-dom@19.2.4(react@19.2.4): + dependencies: + react: 19.2.4 + scheduler: 0.27.0 + + react-is@16.13.1: {} + + react-is@18.3.1: {} + + react-refresh@0.18.0: {} + + react-remove-scroll-bar@2.3.8(@types/react@19.2.10)(react@19.2.4): + dependencies: + react: 19.2.4 + react-style-singleton: 2.2.3(@types/react@19.2.10)(react@19.2.4) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.10 + + react-remove-scroll@2.7.2(@types/react@19.2.10)(react@19.2.4): + dependencies: + react: 19.2.4 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.10)(react@19.2.4) + react-style-singleton: 2.2.3(@types/react@19.2.10)(react@19.2.4) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.2.10)(react@19.2.4) + use-sidecar: 1.1.3(@types/react@19.2.10)(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.10 + + react-router-dom@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + react-router: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + + react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + cookie: 1.1.1 + react: 19.2.4 + set-cookie-parser: 2.7.2 + optionalDependencies: + react-dom: 19.2.4(react@19.2.4) + + react-smooth@4.0.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + fast-equals: 5.4.0 + prop-types: 15.8.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + react-transition-group: 4.4.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + + react-style-singleton@2.2.3(@types/react@19.2.10)(react@19.2.4): + dependencies: + get-nonce: 1.0.1 + react: 19.2.4 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.10 + + react-transition-group@4.4.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + '@babel/runtime': 7.28.6 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + react@19.2.4: {} + + recharts-scale@0.4.5: + dependencies: + decimal.js-light: 2.5.1 + + recharts@2.15.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + clsx: 2.1.1 + eventemitter3: 4.0.7 + lodash: 4.17.23 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + react-is: 18.3.1 + react-smooth: 4.0.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + recharts-scale: 0.4.5 + tiny-invariant: 1.3.3 + victory-vendor: 36.9.2 + + resolve-from@4.0.0: {} + + rollup@4.57.1: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.57.1 + '@rollup/rollup-android-arm64': 4.57.1 + '@rollup/rollup-darwin-arm64': 4.57.1 + '@rollup/rollup-darwin-x64': 4.57.1 + '@rollup/rollup-freebsd-arm64': 4.57.1 + '@rollup/rollup-freebsd-x64': 4.57.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.57.1 + '@rollup/rollup-linux-arm-musleabihf': 4.57.1 + '@rollup/rollup-linux-arm64-gnu': 4.57.1 + '@rollup/rollup-linux-arm64-musl': 4.57.1 + '@rollup/rollup-linux-loong64-gnu': 4.57.1 + '@rollup/rollup-linux-loong64-musl': 4.57.1 + '@rollup/rollup-linux-ppc64-gnu': 4.57.1 + '@rollup/rollup-linux-ppc64-musl': 4.57.1 + '@rollup/rollup-linux-riscv64-gnu': 4.57.1 + '@rollup/rollup-linux-riscv64-musl': 4.57.1 + '@rollup/rollup-linux-s390x-gnu': 4.57.1 + '@rollup/rollup-linux-x64-gnu': 4.57.1 + '@rollup/rollup-linux-x64-musl': 4.57.1 + '@rollup/rollup-openbsd-x64': 4.57.1 + '@rollup/rollup-openharmony-arm64': 4.57.1 + '@rollup/rollup-win32-arm64-msvc': 4.57.1 + '@rollup/rollup-win32-ia32-msvc': 4.57.1 + '@rollup/rollup-win32-x64-gnu': 4.57.1 + '@rollup/rollup-win32-x64-msvc': 4.57.1 + fsevents: 2.3.3 + + scheduler@0.27.0: {} + + semver@6.3.1: {} + + semver@7.7.3: {} + + set-cookie-parser@2.7.2: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + sonner@2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + source-map-js@1.2.1: {} + + strip-json-comments@3.1.1: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + tailwind-merge@3.4.0: {} + + tailwindcss@4.1.18: {} + + tapable@2.3.0: {} + + tiny-invariant@1.3.3: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + ts-api-utils@2.4.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + tslib@2.8.1: {} + + tw-animate-css@1.4.0: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.39.2(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + typescript@5.9.3: {} + + undici-types@7.16.0: {} + + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + use-callback-ref@1.3.3(@types/react@19.2.10)(react@19.2.4): + dependencies: + react: 19.2.4 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.10 + + use-sidecar@1.1.3(@types/react@19.2.10)(react@19.2.4): + dependencies: + detect-node-es: 1.1.0 + react: 19.2.4 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.10 + + victory-vendor@36.9.2: + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-ease': 3.0.2 + '@types/d3-interpolate': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-timer': 3.0.2 + d3-array: 3.2.4 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-scale: 4.0.2 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-timer: 3.0.1 + + vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.30.2): + dependencies: + esbuild: 0.27.2 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.57.1 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 24.10.9 + fsevents: 2.3.3 + jiti: 2.6.1 + lightningcss: 1.30.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + yallist@3.1.1: {} + + yocto-queue@0.1.0: {} + + zod-validation-error@4.0.2(zod@4.3.6): + dependencies: + zod: 4.3.6 + + zod@4.3.6: {} diff --git a/frontend/public/vite.svg b/frontend/public/vite.svg new file mode 100644 index 00000000..e7b8dfb1 --- /dev/null +++ b/frontend/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 00000000..d11e9a2b --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,121 @@ +import { lazy, Suspense } from 'react' +import { BrowserRouter, Routes, Route } from 'react-router-dom' +import { QueryClientProvider } from '@tanstack/react-query' +import { ThemeProvider } from '@/providers/theme-provider' +import { queryClient } from '@/lib/query-client' +import { AppShell } from '@/components/layout/app-shell' +import { LoadingState } from '@/components/common/loading-state' +import { ROUTES } from '@/lib/constants' + +// Lazy-loaded page components +const DashboardPage = lazy(() => import('@/pages/dashboard')) +const SalesExplorerPage = lazy(() => import('@/pages/explorer/sales')) +const StoresExplorerPage = lazy(() => import('@/pages/explorer/stores')) +const ProductsExplorerPage = lazy(() => import('@/pages/explorer/products')) +const RunsExplorerPage = lazy(() => import('@/pages/explorer/runs')) +const JobsMonitorPage = lazy(() => import('@/pages/explorer/jobs')) +const ForecastPage = lazy(() => import('@/pages/visualize/forecast')) +const BacktestPage = lazy(() => import('@/pages/visualize/backtest')) +const ChatPage = lazy(() => import('@/pages/chat')) +const AdminPage = lazy(() => import('@/pages/admin')) + +function PageLoader() { + return +} + +function App() { + return ( + + + + + }> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + + + + + + ) +} + +export default App diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg new file mode 100644 index 00000000..6c87de9b --- /dev/null +++ b/frontend/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/components/charts/backtest-folds-chart.tsx b/frontend/src/components/charts/backtest-folds-chart.tsx new file mode 100644 index 00000000..ec0b082b --- /dev/null +++ b/frontend/src/components/charts/backtest-folds-chart.tsx @@ -0,0 +1,118 @@ +import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Legend, Cell } from 'recharts' +import { + ChartConfig, + ChartContainer, + ChartTooltip, + ChartTooltipContent, +} from '@/components/ui/chart' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' + +interface FoldMetric { + fold: number + mae: number + smape: number + wape: number + bias: number +} + +interface BacktestFoldsChartProps { + title: string + description?: string + data: FoldMetric[] + metricKey?: 'mae' | 'smape' | 'wape' | 'bias' + height?: number + className?: string +} + +const metricColors: Record = { + mae: 'hsl(var(--chart-1))', + smape: 'hsl(var(--chart-2))', + wape: 'hsl(var(--chart-3))', + bias: 'hsl(var(--chart-4))', +} + +const metricLabels: Record = { + mae: 'MAE', + smape: 'sMAPE', + wape: 'WAPE', + bias: 'Bias', +} + +const chartConfig: ChartConfig = { + mae: { label: 'MAE', color: metricColors.mae }, + smape: { label: 'sMAPE', color: metricColors.smape }, + wape: { label: 'WAPE', color: metricColors.wape }, + bias: { label: 'Bias', color: metricColors.bias }, +} + +export function BacktestFoldsChart({ + title, + description, + data, + metricKey = 'mae', + height = 300, + className, +}: BacktestFoldsChartProps) { + const formattedData = data.map((d) => ({ + ...d, + foldLabel: `Fold ${d.fold}`, + })) + + return ( + + + {title} + {description && {description}} + + + + + + + + } /> + + + {formattedData.map((_, index) => ( + + ))} + + + + + + ) +} + +interface MetricsSummaryProps { + metrics: { + label: string + value: number + unit?: string + description?: string + }[] + className?: string +} + +export function MetricsSummary({ metrics, className }: MetricsSummaryProps) { + return ( +
+ {metrics.map((metric) => ( +
+

{metric.label}

+

+ {metric.value.toFixed(2)} + {metric.unit && {metric.unit}} +

+ {metric.description && ( +

{metric.description}

+ )} +
+ ))} +
+ ) +} diff --git a/frontend/src/components/charts/index.ts b/frontend/src/components/charts/index.ts new file mode 100644 index 00000000..1144d92d --- /dev/null +++ b/frontend/src/components/charts/index.ts @@ -0,0 +1,3 @@ +export * from './kpi-card' +export * from './time-series-chart' +export * from './backtest-folds-chart' diff --git a/frontend/src/components/charts/kpi-card.tsx b/frontend/src/components/charts/kpi-card.tsx new file mode 100644 index 00000000..7f9ff6c3 --- /dev/null +++ b/frontend/src/components/charts/kpi-card.tsx @@ -0,0 +1,72 @@ +import { LucideIcon } from 'lucide-react' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { Skeleton } from '@/components/ui/skeleton' +import { cn } from '@/lib/utils' + +interface KPICardProps { + title: string + value: string | number + description?: string + icon?: LucideIcon + trend?: { + value: number + label?: string + } + isLoading?: boolean + className?: string +} + +export function KPICard({ + title, + value, + description, + icon: Icon, + trend, + isLoading = false, + className, +}: KPICardProps) { + if (isLoading) { + return ( + + + + + + + + + + + ) + } + + return ( + + + {title} + {Icon && } + + +
{value}
+
+ {trend && ( + 0 + ? 'text-green-600 dark:text-green-400' + : trend.value < 0 + ? 'text-red-600 dark:text-red-400' + : '' + )} + > + {trend.value > 0 ? '+' : ''} + {trend.value.toFixed(1)}% + + )} + {description && {description}} +
+
+
+ ) +} diff --git a/frontend/src/components/charts/time-series-chart.tsx b/frontend/src/components/charts/time-series-chart.tsx new file mode 100644 index 00000000..7d3dfdfc --- /dev/null +++ b/frontend/src/components/charts/time-series-chart.tsx @@ -0,0 +1,102 @@ +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Legend } from 'recharts' +import { + ChartConfig, + ChartContainer, + ChartTooltip, + ChartTooltipContent, +} from '@/components/ui/chart' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' + +interface TimeSeriesDataPoint { + date: string + actual?: number + predicted?: number + [key: string]: string | number | undefined +} + +interface TimeSeriesChartProps { + title: string + description?: string + data: TimeSeriesDataPoint[] + actualKey?: string + predictedKey?: string + xAxisKey?: string + showActual?: boolean + showPredicted?: boolean + height?: number + className?: string +} + +export function TimeSeriesChart({ + title, + description, + data, + actualKey = 'actual', + predictedKey = 'predicted', + xAxisKey = 'date', + showActual = true, + showPredicted = true, + height = 300, + className, +}: TimeSeriesChartProps) { + const chartConfig: ChartConfig = { + [actualKey]: { + label: 'Actual', + color: 'hsl(var(--chart-1))', + }, + [predictedKey]: { + label: 'Predicted', + color: 'hsl(var(--chart-2))', + }, + } + + return ( + + + {title} + {description && {description}} + + + + + + { + // Format date for display + const date = new Date(value) + return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) + }} + /> + + } /> + + {showActual && ( + + )} + {showPredicted && ( + + )} + + + + + ) +} diff --git a/frontend/src/components/chat/chat-input.tsx b/frontend/src/components/chat/chat-input.tsx new file mode 100644 index 00000000..b4ad2876 --- /dev/null +++ b/frontend/src/components/chat/chat-input.tsx @@ -0,0 +1,120 @@ +import { useState, useRef, useEffect } from 'react' +import { Send, Loader2 } from 'lucide-react' +import { Button } from '@/components/ui/button' +import { Textarea } from '@/components/ui/textarea' + +interface ChatInputProps { + onSend: (message: string) => void + isLoading?: boolean + disabled?: boolean + placeholder?: string +} + +export function ChatInput({ + onSend, + isLoading = false, + disabled = false, + placeholder = 'Type your message...', +}: ChatInputProps) { + const [input, setInput] = useState('') + const textareaRef = useRef(null) + + // Auto-resize textarea + useEffect(() => { + const textarea = textareaRef.current + if (textarea) { + textarea.style.height = 'auto' + textarea.style.height = `${Math.min(textarea.scrollHeight, 200)}px` + } + }, [input]) + + const handleSubmit = () => { + const trimmed = input.trim() + if (!trimmed || isLoading || disabled) return + onSend(trimmed) + setInput('') + } + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault() + handleSubmit() + } + } + + return ( +
+