chore: main refactor plus passkeys 3cd66356
Steve · 2025-11-21 19:35 10 file(s) · +743 −172
CLAUDE.md (added) +147 −0
1 +
# CLAUDE.md
2 +
3 +
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4 +
5 +
## Project Overview
6 +
7 +
Alcove is an end-to-end encrypted RSS reader built with React, Vite, and Evolu (a local-first CRDT database). The app stores all data locally, syncs encrypted data to a WebSocket relay server, and supports mnemonic-based backup/restore.
8 +
9 +
## Development Commands
10 +
11 +
```bash
12 +
# Development server
13 +
npm run dev
14 +
15 +
# Build for production
16 +
npm run build
17 +
18 +
# Type check only
19 +
tsc -b
20 +
21 +
# Lint
22 +
npm run lint
23 +
24 +
# Preview production build
25 +
npm preview
26 +
```
27 +
28 +
## Architecture
29 +
30 +
### Database & State Management (Evolu)
31 +
32 +
**File:** `src/lib/evolu.ts`
33 +
34 +
Alcove uses Evolu, a local-first CRDT database that provides:
35 +
- End-to-end encrypted sync via WebSocket (`wss://relay.alcove.tools`)
36 +
- Mnemonic-based account backup/restore
37 +
- Soft-delete pattern (uses `isDeleted` flag, never hard deletes)
38 +
- Type-safe React hooks via `createUseEvolu`
39 +
40 +
**Critical Pattern:** Always use soft deletes:
41 +
```typescript
42 +
// WRONG - breaks CRDT sync
43 +
evolu.delete(table, id);
44 +
45 +
// CORRECT - use isDeleted flag
46 +
evolu.update(table, { id, isDeleted: sqliteTrue });
47 +
```
48 +
49 +
**Database Schema** (`src/lib/scheme.ts`):
50 +
- `rssFeed` - Feed metadata (url, title, description, category)
51 +
- `rssPost` - Individual posts (feedId, title, link, content, author, publishedDate)
52 +
- `readStatus` - Read/unread tracking per post
53 +
- `userPreferences` - User settings (theme, refresh interval)
54 +
55 +
All string fields use branded types with length constraints. Use pre-built queries in `evolu.ts` for type safety.
56 +
57 +
### RSS Feed Processing
58 +
59 +
**File:** `src/lib/feed-operations.ts`
60 +
61 +
**Feed Discovery Flow:**
62 +
1. Check if URL looks like direct feed (`looksLikeFeedUrl()`)
63 +
2. If not, try common paths: `/feed`, `/rss.xml`, `/atom.xml`, etc. (`discoverFeed()`)
64 +
3. Fetch with CORS proxy fallback (`fetchFeedWithFallback()`)
65 +
4. Parse XML and detect format: RSS 2.0, Atom 1.0, or RDF (`parseFeedXml()`)
66 +
5. Extract and sanitize data to match schema constraints
67 +
6. Insert into database
68 +
69 +
**Content Extraction:** Handles multiple field variations (`content:encoded`, `content`, `description`, `summary`) and format differences (CDATA, nested objects, strings).
70 +
71 +
**IMPORTANT:** Always sanitize feed data with `sanitizeFeedData()` and `sanitizePostData()` before inserting to prevent schema validation errors from truncated strings.
72 +
73 +
### OPML Import/Export
74 +
75 +
**File:** `src/lib/opml.ts`
76 +
77 +
- **Export:** Groups feeds by category, generates XML with hierarchy, triggers download
78 +
- **Import:** Parses OPML, fetches each feed's XML, extracts posts, inserts with category
79 +
- Progress tracking via Sonner toasts with success/error counts
80 +
81 +
### Component Structure
82 +
83 +
```
84 +
App.tsx
85 +
├── Initial onboarding (if no feeds)
86 +
├── Dashboard (main app)
87 +
│   ├── AppSidebar
88 +
│   │   ├── NavFeeds (category-grouped feeds)
89 +
│   │   ├── PostsList (search, filter, read status)
90 +
│   │   └── NavUser (settings, OPML, backup/restore)
91 +
│   └── Post content view (markdown rendering)
92 +
```
93 +
94 +
**Mobile Responsiveness:** Uses `useSidebar` hook and `md:` breakpoints. Sidebar toggles between feeds/posts views on mobile.
95 +
96 +
**Markdown Rendering:** Posts use `react-markdown` with `rehype-sanitize` for XSS protection and `rehype-raw` for HTML support. Relative image URLs are converted to absolute.
97 +
98 +
## Key Patterns
99 +
100 +
### Type Narrowing
101 +
Always narrow query results to eliminate null fields:
102 +
```typescript
103 +
const feeds = useQuery(allFeedsQuery).$narrowType(/* ... */);
104 +
```
105 +
106 +
### Search & Filter
107 +
Client-side filtering for instant feedback:
108 +
```typescript
109 +
const filtered = useMemo(() => {
110 +
  return posts.filter(p => p.title.includes(searchQuery))
111 +
             .sort((a, b) => /* date desc */);
112 +
}, [posts, searchQuery]);
113 +
```
114 +
115 +
### Error Handling
116 +
- User-friendly toast messages for errors
117 +
- Console logs for debugging
118 +
- Continue processing on partial failures (e.g., OPML import with some invalid feeds)
119 +
120 +
### State Management
121 +
- React `useState` for UI state
122 +
- Evolu for persistent, synced data
123 +
- `localStorage` for initialization flags
124 +
125 +
## Important Files
126 +
127 +
- `src/lib/evolu.ts` - Database setup, queries, hooks
128 +
- `src/lib/scheme.ts` - Database schema and types
129 +
- `src/lib/feed-operations.ts` - RSS fetching and parsing
130 +
- `src/lib/opml.ts` - OPML import/export
131 +
- `src/components/app-sidebar.tsx` - Main navigation
132 +
- `src/components/dashboard.tsx` - Post content view
133 +
- `src/App.tsx` - Root component and feed management
134 +
135 +
## Path Aliases
136 +
137 +
TypeScript/Vite configured with `@/*` alias mapping to `./src/*`:
138 +
```typescript
139 +
import { evolu } from "@/lib/evolu";
140 +
```
141 +
142 +
## Testing Feeds
143 +
144 +
When testing feed discovery or parsing, use these patterns:
145 +
- Common paths: `/feed`, `/rss.xml`, `/atom.xml`, `/rss`, `/feed.xml`, `/index.xml`
146 +
- Formats supported: RSS 2.0, Atom 1.0, RDF/RSS 1.0
147 +
- CORS proxy: `https://corsproxy.io/?{encodeURIComponent(url)}`
bun.lock +159 −67
1 1
{
2 2
  "lockfileVersion": 1,
3 +
  "configVersion": 0,
3 4
  "workspaces": {
4 5
    "": {
5 6
      "name": "alcove",
6 7
      "dependencies": {
7 -
        "@evolu/common": "^6.0.1-preview.18",
8 -
        "@evolu/react": "^9.0.1-preview.4",
9 -
        "@evolu/react-web": "^1.0.1-preview.4",
10 -
        "@radix-ui/react-avatar": "^1.1.10",
8 +
        "@evolu/common": "^7.2.0",
9 +
        "@evolu/react": "^10.2.0",
10 +
        "@evolu/react-web": "^2.2.0",
11 +
        "@evolu/web": "^2.2.0",
12 +
        "@radix-ui/react-avatar": "^1.1.11",
11 13
        "@radix-ui/react-collapsible": "^1.1.12",
12 14
        "@radix-ui/react-context-menu": "^2.2.16",
13 15
        "@radix-ui/react-dialog": "^1.1.15",
14 16
        "@radix-ui/react-dropdown-menu": "^2.1.16",
15 -
        "@radix-ui/react-label": "^2.1.7",
17 +
        "@radix-ui/react-label": "^2.1.8",
16 18
        "@radix-ui/react-popover": "^1.1.15",
17 19
        "@radix-ui/react-select": "^2.2.6",
18 -
        "@radix-ui/react-separator": "^1.1.7",
19 -
        "@radix-ui/react-slot": "^1.2.3",
20 +
        "@radix-ui/react-separator": "^1.1.8",
21 +
        "@radix-ui/react-slot": "^1.2.4",
20 22
        "@radix-ui/react-switch": "^1.2.6",
21 23
        "@radix-ui/react-tooltip": "^1.2.8",
22 24
        "@tailwindcss/typography": "^0.5.19",
23 -
        "@tailwindcss/vite": "^4.1.16",
25 +
        "@tailwindcss/vite": "^4.1.17",
24 26
        "class-variance-authority": "^0.7.1",
25 27
        "clsx": "^2.1.1",
26 -
        "fast-xml-parser": "^5.3.1",
28 +
        "fast-xml-parser": "^5.3.2",
27 29
        "lucide-react": "^0.548.0",
28 30
        "next-themes": "^0.4.6",
29 31
        "react": "^19.2.0",
33 35
        "rehype-sanitize": "^6.0.0",
34 36
        "remark-gfm": "^4.0.1",
35 37
        "sonner": "^2.0.7",
36 -
        "tailwind-merge": "^3.3.1",
37 -
        "tailwindcss": "^4.1.16",
38 +
        "tailwind-merge": "^3.4.0",
39 +
        "tailwindcss": "^4.1.17",
38 40
      },
39 41
      "devDependencies": {
40 -
        "@eslint/js": "^9.39.0",
41 -
        "@types/node": "^24.10.0",
42 -
        "@types/react": "^19.2.2",
43 -
        "@types/react-dom": "^19.2.2",
44 -
        "@vitejs/plugin-react": "^5.1.0",
45 -
        "eslint": "^9.39.0",
42 +
        "@eslint/js": "^9.39.1",
43 +
        "@types/node": "^24.10.1",
44 +
        "@types/react": "^19.2.6",
45 +
        "@types/react-dom": "^19.2.3",
46 +
        "@vitejs/plugin-react": "^5.1.1",
47 +
        "eslint": "^9.39.1",
46 48
        "eslint-plugin-react-hooks": "^5.2.0",
47 49
        "eslint-plugin-react-refresh": "^0.4.24",
48 50
        "globals": "^16.5.0",
49 51
        "tw-animate-css": "^1.4.0",
50 52
        "typescript": "~5.9.3",
51 -
        "typescript-eslint": "^8.46.2",
53 +
        "typescript-eslint": "^8.47.0",
52 54
        "vite": "npm:rolldown-vite@7.1.14",
53 55
      },
54 56
    },
113 115
114 116
    "@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="],
115 117
116 -
    "@eslint/js": ["@eslint/js@9.39.0", "", {}, "sha512-BIhe0sW91JGPiaF1mOuPy5v8NflqfjIcDNpC+LbW9f609WVRX1rArrhi6Z2ymvrAry9jw+5POTj4t2t62o8Bmw=="],
118 +
    "@eslint/js": ["@eslint/js@9.39.1", "", {}, "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw=="],
117 119
118 120
    "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
119 121
120 122
    "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
121 123
122 -
    "@evolu/common": ["@evolu/common@6.0.1-preview.21", "", { "dependencies": { "@noble/ciphers": "^2.0.0", "@noble/hashes": "^2.0.0", "@scure/bip39": "^2.0.0", "kysely": "^0.28.4", "msgpackr": "^1.11.5", "random": "^5.4.1" } }, "sha512-FEg40ttU7N5YlRNYlsUwZDa56IvwHUnh8N+mqDCmiIGMcKfcJI+xIQp8/dBbjIQMzqaSlhqbv3bEfJenEmppQw=="],
124 +
    "@evolu/common": ["@evolu/common@7.2.0", "", { "dependencies": { "@noble/ciphers": "^2.0.0", "@noble/hashes": "^2.0.0", "@scure/bip39": "^2.0.0", "kysely": "^0.28.4", "msgpackr": "^1.11.5", "random": "^5.4.1" } }, "sha512-5N0dBl/4ClUIt1X0dz4D5XTOz3stEbu9A8Pl0Mm4rkyURZ/eHJGGy0bu5M541uz7yyhxQ8d1BTTVqbi/D4XNyw=="],
123 125
124 -
    "@evolu/react": ["@evolu/react@9.0.1-preview.5", "", { "peerDependencies": { "@evolu/common": "^6.0.1-preview.20", "react": ">=19" } }, "sha512-/HxQ3lpDnffxq9w/mLHc4r7dvqydaZ/8cHxp20AKUwZed+QTyRJ/NM0meFBAsmve0K5K7RmQz8+wC4Ux8BWQnQ=="],
126 +
    "@evolu/react": ["@evolu/react@10.2.0", "", { "peerDependencies": { "@evolu/common": "^7.2.0", "react": ">=19" } }, "sha512-C4FsGlbO6lNgZXRwzcidGfG+t2Mrt+u/LLlex8D3JxY+16fq4U/nsHYB+0juxmHf1DEPRjAc39BYm/2+CHV+3A=="],
125 127
126 -
    "@evolu/react-web": ["@evolu/react-web@1.0.1-preview.4", "", { "dependencies": { "blo": "^2.0.0" }, "peerDependencies": { "@evolu/common": "^6.0.1-preview.20", "@evolu/web": "^1.0.1-preview.6", "react": ">=19", "react-dom": ">=19" } }, "sha512-XN8os6hqY8eQae2jPjYfmF12nKsVty9Jx9FF+bmA7iUXHR0rxE4hGu7x7zMg9DCCUjoRyNfx0JwIMklRMcn+XQ=="],
128 +
    "@evolu/react-web": ["@evolu/react-web@2.2.0", "", { "peerDependencies": { "@evolu/common": "^7.2.0", "@evolu/web": "^2.2.0", "react": ">=19", "react-dom": ">=19" } }, "sha512-PiJpG2a7rAWG+VbPlx9zkEcCjb0kTXUxuBh0csqMXZcTklmt4cYOkRx+y30EMl8dYIohe2DGQdxLrCS5cxms4Q=="],
127 129
128 130
    "@evolu/sqlite-wasm": ["@evolu/sqlite-wasm@2.2.4", "", { "bin": { "sqlite-wasm": "bin/index.js" } }, "sha512-/JOYGFN93QspD2C8HVxVgBUlFWqJ1IpaVuIhEB53u4+ZvE+D3LjpNHDYiwZgf0n7VaH2U85OY5eV3wUrWc3scg=="],
129 131
130 -
    "@evolu/web": ["@evolu/web@1.0.1-preview.6", "", { "dependencies": { "@evolu/sqlite-wasm": "2.2.4", "idb-keyval": "^6.2.2" }, "peerDependencies": { "@evolu/common": "^6.0.1-preview.20" } }, "sha512-y2OnLOWi+TvLIiC1Whm7KJvZIlJ59drrMaMMsjQph4TfdfwdTJEbisrOB/bfYt6aQd5mGutfAJ0mF9zJ/IPkmg=="],
132 +
    "@evolu/web": ["@evolu/web@2.2.0", "", { "dependencies": { "@evolu/sqlite-wasm": "2.2.4", "idb-keyval": "^6.2.2" }, "peerDependencies": { "@evolu/common": "^7.2.0" } }, "sha512-sOUKBI+uEAG9qa5nzWK3blxQLgHV4Zq8KYpAAKM59Aj2QqPGrRdijDFiu3GJrSvaswdCfs/zQIXdredhhRr9EQ=="],
131 133
132 134
    "@floating-ui/core": ["@floating-ui/core@1.7.3", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w=="],
133 135
189 191
190 192
    "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w=="],
191 193
192 -
    "@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.10", "", { "dependencies": { "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog=="],
194 +
    "@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.11", "", { "dependencies": { "@radix-ui/react-context": "1.1.3", "@radix-ui/react-primitive": "2.1.4", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q=="],
193 195
194 196
    "@radix-ui/react-collapsible": ["@radix-ui/react-collapsible@1.1.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA=="],
195 197
197 199
198 200
    "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="],
199 201
200 -
    "@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
202 +
    "@radix-ui/react-context": ["@radix-ui/react-context@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw=="],
201 203
202 204
    "@radix-ui/react-context-menu": ["@radix-ui/react-context-menu@2.2.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww=="],
203 205
215 217
216 218
    "@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="],
217 219
218 -
    "@radix-ui/react-label": ["@radix-ui/react-label@2.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ=="],
220 +
    "@radix-ui/react-label": ["@radix-ui/react-label@2.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.4" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A=="],
219 221
220 222
    "@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg=="],
221 223
227 229
228 230
    "@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="],
229 231
230 -
    "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
232 +
    "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="],
231 233
232 234
    "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="],
233 235
234 236
    "@radix-ui/react-select": ["@radix-ui/react-select@2.2.6", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ=="],
235 237
236 -
    "@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA=="],
238 +
    "@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.4" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g=="],
237 239
238 -
    "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
240 +
    "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="],
239 241
240 242
    "@radix-ui/react-switch": ["@radix-ui/react-switch@1.2.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ=="],
241 243
291 293
292 294
    "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-beta.41", "", { "os": "win32", "cpu": "x64" }, "sha512-UlpxKmFdik0Y2VjZrgUCgoYArZJiZllXgIipdBRV1hw6uK45UbQabSTW6Kp6enuOu7vouYWftwhuxfpE8J2JAg=="],
293 295
294 -
    "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.43", "", {}, "sha512-5Uxg7fQUCmfhax7FJke2+8B6cqgeUJUD9o2uXIKXhD+mG0mL6NObmVoi9wXEU1tY89mZKgAYA6fTbftx3q2ZPQ=="],
296 +
    "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.47", "", {}, "sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw=="],
295 297
296 298
    "@scure/base": ["@scure/base@2.0.0", "", {}, "sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w=="],
297 299
298 300
    "@scure/bip39": ["@scure/bip39@2.0.1", "", { "dependencies": { "@noble/hashes": "2.0.1", "@scure/base": "2.0.0" } }, "sha512-PsxdFj/d2AcJcZDX1FXN3dDgitDDTmwf78rKZq1a6c1P1Nan1X/Sxc7667zU3U+AN60g7SxxP0YCVw2H/hBycg=="],
299 301
300 -
    "@tailwindcss/node": ["@tailwindcss/node@4.1.16", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.19", "source-map-js": "^1.2.1", "tailwindcss": "4.1.16" } }, "sha512-BX5iaSsloNuvKNHRN3k2RcCuTEgASTo77mofW0vmeHkfrDWaoFAFvNHpEgtu0eqyypcyiBkDWzSMxJhp3AUVcw=="],
302 +
    "@tailwindcss/node": ["@tailwindcss/node@4.1.17", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.17" } }, "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg=="],
301 303
302 -
    "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.16", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.16", "@tailwindcss/oxide-darwin-arm64": "4.1.16", "@tailwindcss/oxide-darwin-x64": "4.1.16", "@tailwindcss/oxide-freebsd-x64": "4.1.16", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.16", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.16", "@tailwindcss/oxide-linux-arm64-musl": "4.1.16", "@tailwindcss/oxide-linux-x64-gnu": "4.1.16", "@tailwindcss/oxide-linux-x64-musl": "4.1.16", "@tailwindcss/oxide-wasm32-wasi": "4.1.16", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.16", "@tailwindcss/oxide-win32-x64-msvc": "4.1.16" } }, "sha512-2OSv52FRuhdlgyOQqgtQHuCgXnS8nFSYRp2tJ+4WZXKgTxqPy7SMSls8c3mPT5pkZ17SBToGM5LHEJBO7miEdg=="],
304 +
    "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.17", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.17", "@tailwindcss/oxide-darwin-arm64": "4.1.17", "@tailwindcss/oxide-darwin-x64": "4.1.17", "@tailwindcss/oxide-freebsd-x64": "4.1.17", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17", "@tailwindcss/oxide-linux-arm64-musl": "4.1.17", "@tailwindcss/oxide-linux-x64-gnu": "4.1.17", "@tailwindcss/oxide-linux-x64-musl": "4.1.17", "@tailwindcss/oxide-wasm32-wasi": "4.1.17", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17", "@tailwindcss/oxide-win32-x64-msvc": "4.1.17" } }, "sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA=="],
303 305
304 -
    "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.16", "", { "os": "android", "cpu": "arm64" }, "sha512-8+ctzkjHgwDJ5caq9IqRSgsP70xhdhJvm+oueS/yhD5ixLhqTw9fSL1OurzMUhBwE5zK26FXLCz2f/RtkISqHA=="],
306 +
    "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.17", "", { "os": "android", "cpu": "arm64" }, "sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ=="],
305 307
306 -
    "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.16", "", { "os": "darwin", "cpu": "arm64" }, "sha512-C3oZy5042v2FOALBZtY0JTDnGNdS6w7DxL/odvSny17ORUnaRKhyTse8xYi3yKGyfnTUOdavRCdmc8QqJYwFKA=="],
308 +
    "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.17", "", { "os": "darwin", "cpu": "arm64" }, "sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg=="],
307 309
308 -
    "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.16", "", { "os": "darwin", "cpu": "x64" }, "sha512-vjrl/1Ub9+JwU6BP0emgipGjowzYZMjbWCDqwA2Z4vCa+HBSpP4v6U2ddejcHsolsYxwL5r4bPNoamlV0xDdLg=="],
310 +
    "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.17", "", { "os": "darwin", "cpu": "x64" }, "sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog=="],
309 311
310 -
    "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.16", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TSMpPYpQLm+aR1wW5rKuUuEruc/oOX3C7H0BTnPDn7W/eMw8W+MRMpiypKMkXZfwH8wqPIRKppuZoedTtNj2tg=="],
312 +
    "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.17", "", { "os": "freebsd", "cpu": "x64" }, "sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g=="],
311 313
312 -
    "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.16", "", { "os": "linux", "cpu": "arm" }, "sha512-p0GGfRg/w0sdsFKBjMYvvKIiKy/LNWLWgV/plR4lUgrsxFAoQBFrXkZ4C0w8IOXfslB9vHK/JGASWD2IefIpvw=="],
314 +
    "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17", "", { "os": "linux", "cpu": "arm" }, "sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ=="],
313 315
314 -
    "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-DoixyMmTNO19rwRPdqviTrG1rYzpxgyYJl8RgQvdAQUzxC1ToLRqtNJpU/ATURSKgIg6uerPw2feW0aS8SNr/w=="],
316 +
    "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ=="],
315 317
316 -
    "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-H81UXMa9hJhWhaAUca6bU2wm5RRFpuHImrwXBUvPbYb+3jo32I9VIwpOX6hms0fPmA6f2pGVlybO6qU8pF4fzQ=="],
318 +
    "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg=="],
317 319
318 -
    "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.16", "", { "os": "linux", "cpu": "x64" }, "sha512-ZGHQxDtFC2/ruo7t99Qo2TTIvOERULPl5l0K1g0oK6b5PGqjYMga+FcY1wIUnrUxY56h28FxybtDEla+ICOyew=="],
320 +
    "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.17", "", { "os": "linux", "cpu": "x64" }, "sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ=="],
319 321
320 -
    "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.16", "", { "os": "linux", "cpu": "x64" }, "sha512-Oi1tAaa0rcKf1Og9MzKeINZzMLPbhxvm7rno5/zuP1WYmpiG0bEHq4AcRUiG2165/WUzvxkW4XDYCscZWbTLZw=="],
322 +
    "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.17", "", { "os": "linux", "cpu": "x64" }, "sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ=="],
321 323
322 -
    "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.16", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.0.7", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-B01u/b8LteGRwucIBmCQ07FVXLzImWESAIMcUU6nvFt/tYsQ6IHz8DmZ5KtvmwxD+iTYBtM1xwoGXswnlu9v0Q=="],
324 +
    "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.17", "", { "dependencies": { "@emnapi/core": "^1.6.0", "@emnapi/runtime": "^1.6.0", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.0.7", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg=="],
323 325
324 -
    "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.16", "", { "os": "win32", "cpu": "arm64" }, "sha512-zX+Q8sSkGj6HKRTMJXuPvOcP8XfYON24zJBRPlszcH1Np7xuHXhWn8qfFjIujVzvH3BHU+16jBXwgpl20i+v9A=="],
326 +
    "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.17", "", { "os": "win32", "cpu": "arm64" }, "sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A=="],
325 327
326 -
    "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.16", "", { "os": "win32", "cpu": "x64" }, "sha512-m5dDFJUEejbFqP+UXVstd4W/wnxA4F61q8SoL+mqTypId2T2ZpuxosNSgowiCnLp2+Z+rivdU0AqpfgiD7yCBg=="],
328 +
    "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.17", "", { "os": "win32", "cpu": "x64" }, "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw=="],
327 329
328 330
    "@tailwindcss/typography": ["@tailwindcss/typography@0.5.19", "", { "dependencies": { "postcss-selector-parser": "6.0.10" }, "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg=="],
329 331
330 -
    "@tailwindcss/vite": ["@tailwindcss/vite@4.1.16", "", { "dependencies": { "@tailwindcss/node": "4.1.16", "@tailwindcss/oxide": "4.1.16", "tailwindcss": "4.1.16" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-bbguNBcDxsRmi9nnlWJxhfDWamY3lmcyACHcdO1crxfzuLpOhHLLtEIN/nCbbAtj5rchUgQD17QVAKi1f7IsKg=="],
332 +
    "@tailwindcss/vite": ["@tailwindcss/vite@4.1.17", "", { "dependencies": { "@tailwindcss/node": "4.1.17", "@tailwindcss/oxide": "4.1.17", "tailwindcss": "4.1.17" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-4+9w8ZHOiGnpcGI6z1TVVfWaX/koK7fKeSYF3qlYg2xpBtbteP2ddBxiarL+HVgfSJGeK5RIxRQmKm4rTJJAwA=="],
331 333
332 334
    "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
333 335
353 355
354 356
    "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
355 357
356 -
    "@types/node": ["@types/node@24.10.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A=="],
358 +
    "@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
357 359
358 -
    "@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="],
360 +
    "@types/react": ["@types/react@19.2.6", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w=="],
359 361
360 -
    "@types/react-dom": ["@types/react-dom@19.2.2", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw=="],
362 +
    "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
361 363
362 364
    "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
363 365
364 -
    "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.46.2", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.46.2", "@typescript-eslint/type-utils": "8.46.2", "@typescript-eslint/utils": "8.46.2", "@typescript-eslint/visitor-keys": "8.46.2", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.46.2", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ZGBMToy857/NIPaaCucIUQgqueOiq7HeAKkhlvqVV4lm089zUFW6ikRySx2v+cAhKeUCPuWVHeimyk6Dw1iY3w=="],
366 +
    "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.47.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.47.0", "@typescript-eslint/type-utils": "8.47.0", "@typescript-eslint/utils": "8.47.0", "@typescript-eslint/visitor-keys": "8.47.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.47.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-fe0rz9WJQ5t2iaLfdbDc9T80GJy0AeO453q8C3YCilnGozvOyCG5t+EZtg7j7D88+c3FipfP/x+wzGnh1xp8ZA=="],
365 367
366 -
    "@typescript-eslint/parser": ["@typescript-eslint/parser@8.46.2", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.46.2", "@typescript-eslint/types": "8.46.2", "@typescript-eslint/typescript-estree": "8.46.2", "@typescript-eslint/visitor-keys": "8.46.2", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g=="],
368 +
    "@typescript-eslint/parser": ["@typescript-eslint/parser@8.47.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.47.0", "@typescript-eslint/types": "8.47.0", "@typescript-eslint/typescript-estree": "8.47.0", "@typescript-eslint/visitor-keys": "8.47.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ=="],
367 369
368 -
    "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.46.2", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.46.2", "@typescript-eslint/types": "^8.46.2", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-PULOLZ9iqwI7hXcmL4fVfIsBi6AN9YxRc0frbvmg8f+4hQAjQ5GYNKK0DIArNo+rOKmR/iBYwkpBmnIwin4wBg=="],
370 +
    "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.47.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.47.0", "@typescript-eslint/types": "^8.47.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-2X4BX8hUeB5JcA1TQJ7GjcgulXQ+5UkNb0DL8gHsHUHdFoiCTJoYLTpib3LtSDPZsRET5ygN4qqIWrHyYIKERA=="],
369 371
370 -
    "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.46.2", "", { "dependencies": { "@typescript-eslint/types": "8.46.2", "@typescript-eslint/visitor-keys": "8.46.2" } }, "sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA=="],
372 +
    "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.47.0", "", { "dependencies": { "@typescript-eslint/types": "8.47.0", "@typescript-eslint/visitor-keys": "8.47.0" } }, "sha512-a0TTJk4HXMkfpFkL9/WaGTNuv7JWfFTQFJd6zS9dVAjKsojmv9HT55xzbEpnZoY+VUb+YXLMp+ihMLz/UlZfDg=="],
371 373
372 -
    "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.46.2", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-a7QH6fw4S57+F5y2FIxxSDyi5M4UfGF+Jl1bCGd7+L4KsaUY80GsiF/t0UoRFDHAguKlBaACWJRmdrc6Xfkkag=="],
374 +
    "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.47.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ybUAvjy4ZCL11uryalkKxuT3w3sXJAuWhOoGS3T/Wu+iUu1tGJmk5ytSY8gbdACNARmcYEB0COksD2j6hfGK2g=="],
373 375
374 -
    "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.46.2", "", { "dependencies": { "@typescript-eslint/types": "8.46.2", "@typescript-eslint/typescript-estree": "8.46.2", "@typescript-eslint/utils": "8.46.2", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-HbPM4LbaAAt/DjxXaG9yiS9brOOz6fabal4uvUmaUYe6l3K1phQDMQKBRUrr06BQkxkvIZVVHttqiybM9nJsLA=="],
376 +
    "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.47.0", "", { "dependencies": { "@typescript-eslint/types": "8.47.0", "@typescript-eslint/typescript-estree": "8.47.0", "@typescript-eslint/utils": "8.47.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-QC9RiCmZ2HmIdCEvhd1aJELBlD93ErziOXXlHEZyuBo3tBiAZieya0HLIxp+DoDWlsQqDawyKuNEhORyku+P8A=="],
375 377
376 -
    "@typescript-eslint/types": ["@typescript-eslint/types@8.46.2", "", {}, "sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ=="],
378 +
    "@typescript-eslint/types": ["@typescript-eslint/types@8.47.0", "", {}, "sha512-nHAE6bMKsizhA2uuYZbEbmp5z2UpffNrPEqiKIeN7VsV6UY/roxanWfoRrf6x/k9+Obf+GQdkm0nPU+vnMXo9A=="],
377 379
378 -
    "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.46.2", "", { "dependencies": { "@typescript-eslint/project-service": "8.46.2", "@typescript-eslint/tsconfig-utils": "8.46.2", "@typescript-eslint/types": "8.46.2", "@typescript-eslint/visitor-keys": "8.46.2", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-f7rW7LJ2b7Uh2EiQ+7sza6RDZnajbNbemn54Ob6fRwQbgcIn+GWfyuHDHRYgRoZu1P4AayVScrRW+YfbTvPQoQ=="],
380 +
    "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.47.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.47.0", "@typescript-eslint/tsconfig-utils": "8.47.0", "@typescript-eslint/types": "8.47.0", "@typescript-eslint/visitor-keys": "8.47.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-k6ti9UepJf5NpzCjH31hQNLHQWupTRPhZ+KFF8WtTuTpy7uHPfeg2NM7cP27aCGajoEplxJDFVCEm9TGPYyiVg=="],
379 381
380 -
    "@typescript-eslint/utils": ["@typescript-eslint/utils@8.46.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.46.2", "@typescript-eslint/types": "8.46.2", "@typescript-eslint/typescript-estree": "8.46.2" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-sExxzucx0Tud5tE0XqR0lT0psBQvEpnpiul9XbGUB1QwpWJJAps1O/Z7hJxLGiZLBKMCutjTzDgmd1muEhBnVg=="],
382 +
    "@typescript-eslint/utils": ["@typescript-eslint/utils@8.47.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.47.0", "@typescript-eslint/types": "8.47.0", "@typescript-eslint/typescript-estree": "8.47.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-g7XrNf25iL4TJOiPqatNuaChyqt49a/onq5YsJ9+hXeugK+41LVg7AxikMfM02PC6jbNtZLCJj6AUcQXJS/jGQ=="],
381 383
382 -
    "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.46.2", "", { "dependencies": { "@typescript-eslint/types": "8.46.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w=="],
384 +
    "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.47.0", "", { "dependencies": { "@typescript-eslint/types": "8.47.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-SIV3/6eftCy1bNzCQoPmbWsRLujS8t5iDIZ4spZOBHqrM+yfX2ogg8Tt3PDTAVKw3sSCiUgg30uOAvK2r9zGjQ=="],
383 385
384 386
    "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
385 387
386 -
    "@vitejs/plugin-react": ["@vitejs/plugin-react@5.1.0", "", { "dependencies": { "@babel/core": "^7.28.4", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.43", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-4LuWrg7EKWgQaMJfnN+wcmbAW+VSsCmqGohftWjuct47bv8uE4n/nPpq4XjJPsxgq00GGG5J8dvBczp8uxScew=="],
388 +
    "@vitejs/plugin-react": ["@vitejs/plugin-react@5.1.1", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.47", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-WQfkSw0QbQ5aJ2CHYw23ZGkqnRwqKHD/KYsMeTkZzPT4Jcf0DcBxBtwMJxnu6E7oxw5+JC6ZAiePgh28uJ1HBA=="],
387 389
388 390
    "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
389 391
405 407
406 408
    "baseline-browser-mapping": ["baseline-browser-mapping@2.8.23", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-616V5YX4bepJFzNyOfce5Fa8fDJMfoxzOIzDCZwaGL8MKVpFrXqfNUoIpRn9YMI5pXf/VKgzjB4htFMsFKKdiQ=="],
407 409
408 -
    "blo": ["blo@2.0.0", "", {}, "sha512-VUUr1+vWisNSaHVswkbPbk1+GhygClKILkGchae7nsyuJ4ZVz5l1hEW4eR5F/ly/asM5vzigYGXjPnvrd//CVg=="],
409 -
410 410
    "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
411 411
412 412
    "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
447 447
448 448
    "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
449 449
450 -
    "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
450 +
    "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
451 451
452 452
    "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
453 453
473 473
474 474
    "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
475 475
476 -
    "eslint": ["eslint@9.39.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.0", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "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.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-iy2GE3MHrYTL5lrCtMZ0X1KLEKKUjmK0kzwcnefhR66txcEmXZD2YWgR5GNdcEwkNx3a0siYkSvl0vIC+Svjmg=="],
476 +
    "eslint": ["eslint@9.39.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.1", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "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.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g=="],
477 477
478 478
    "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@5.2.0", "", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg=="],
479 479
505 505
506 506
    "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
507 507
508 -
    "fast-xml-parser": ["fast-xml-parser@5.3.1", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-jbNkWiv2Ec1A7wuuxk0br0d0aTMUtQ4IkL+l/i1r9PRf6pLXjDgsBsWwO+UyczmQlnehi4Tbc8/KIvxGQe+I/A=="],
508 +
    "fast-xml-parser": ["fast-xml-parser@5.3.2", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-n8v8b6p4Z1sMgqRmqLJm3awW4NX7NkaKPfb3uJIBTSH7Pdvufi3PQ3/lJLQrvxcMYl7JI2jnDO90siPEpD8JBA=="],
509 509
510 510
    "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
511 511
847 847
848 848
    "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
849 849
850 -
    "tailwind-merge": ["tailwind-merge@3.3.1", "", {}, "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g=="],
850 +
    "tailwind-merge": ["tailwind-merge@3.4.0", "", {}, "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g=="],
851 851
852 -
    "tailwindcss": ["tailwindcss@4.1.16", "", {}, "sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA=="],
852 +
    "tailwindcss": ["tailwindcss@4.1.17", "", {}, "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q=="],
853 853
854 854
    "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
855 855
871 871
872 872
    "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
873 873
874 -
    "typescript-eslint": ["typescript-eslint@8.46.2", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.46.2", "@typescript-eslint/parser": "8.46.2", "@typescript-eslint/typescript-estree": "8.46.2", "@typescript-eslint/utils": "8.46.2" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-vbw8bOmiuYNdzzV3lsiWv6sRwjyuKJMQqWulBOU7M0RrxedXledX8G8kBbQeiOYDnTfiXz0Y4081E1QMNB6iQg=="],
874 +
    "typescript-eslint": ["typescript-eslint@8.47.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.47.0", "@typescript-eslint/parser": "8.47.0", "@typescript-eslint/typescript-estree": "8.47.0", "@typescript-eslint/utils": "8.47.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Lwe8i2XQ3WoMjua/r1PHrCTpkubPYJCAfOurtn+mtTzqB6jNd+14n9UN1bJ4s3F49x9ixAm0FLflB/JzQ57M8Q=="],
875 875
876 876
    "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
877 877
923 923
924 924
    "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
925 925
926 +
    "@radix-ui/react-arrow/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
927 +
928 +
    "@radix-ui/react-collapsible/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
929 +
930 +
    "@radix-ui/react-collapsible/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
931 +
932 +
    "@radix-ui/react-collection/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
933 +
934 +
    "@radix-ui/react-collection/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
935 +
936 +
    "@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
937 +
938 +
    "@radix-ui/react-context-menu/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
939 +
940 +
    "@radix-ui/react-context-menu/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
941 +
942 +
    "@radix-ui/react-dialog/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
943 +
944 +
    "@radix-ui/react-dialog/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
945 +
946 +
    "@radix-ui/react-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
947 +
948 +
    "@radix-ui/react-dismissable-layer/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
949 +
950 +
    "@radix-ui/react-dropdown-menu/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
951 +
952 +
    "@radix-ui/react-dropdown-menu/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
953 +
954 +
    "@radix-ui/react-focus-scope/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
955 +
956 +
    "@radix-ui/react-menu/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
957 +
958 +
    "@radix-ui/react-menu/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
959 +
960 +
    "@radix-ui/react-menu/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
961 +
962 +
    "@radix-ui/react-popover/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
963 +
964 +
    "@radix-ui/react-popover/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
965 +
966 +
    "@radix-ui/react-popover/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
967 +
968 +
    "@radix-ui/react-popper/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
969 +
970 +
    "@radix-ui/react-popper/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
971 +
972 +
    "@radix-ui/react-portal/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
973 +
974 +
    "@radix-ui/react-roving-focus/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
975 +
976 +
    "@radix-ui/react-roving-focus/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
977 +
978 +
    "@radix-ui/react-select/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
979 +
980 +
    "@radix-ui/react-select/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
981 +
982 +
    "@radix-ui/react-select/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
983 +
984 +
    "@radix-ui/react-switch/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
985 +
986 +
    "@radix-ui/react-switch/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
987 +
988 +
    "@radix-ui/react-tooltip/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
989 +
990 +
    "@radix-ui/react-tooltip/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
991 +
992 +
    "@radix-ui/react-tooltip/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
993 +
994 +
    "@radix-ui/react-visually-hidden/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
995 +
926 996
    "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.6.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-zq/ay+9fNIJJtJiZxdTnXS20PllcYMX3OE23ESc4HK/bdYu3cOWYVhsOhVnXALfU/uqJIxn5NBPd9z4v+SfoSg=="],
927 997
928 998
    "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.6.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA=="],
952 1022
    "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
953 1023
954 1024
    "rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.41", "", {}, "sha512-ycMEPrS3StOIeb87BT3/+bu+blEtyvwQ4zmo2IcJQy0Rd1DAAhKksA0iUZ3MYSpJtjlPhg0Eo6mvVS6ggPhRbw=="],
1025 +
1026 +
    "@radix-ui/react-arrow/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
1027 +
1028 +
    "@radix-ui/react-collapsible/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
1029 +
1030 +
    "@radix-ui/react-context-menu/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
1031 +
1032 +
    "@radix-ui/react-dismissable-layer/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
1033 +
1034 +
    "@radix-ui/react-dropdown-menu/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
1035 +
1036 +
    "@radix-ui/react-focus-scope/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
1037 +
1038 +
    "@radix-ui/react-popper/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
1039 +
1040 +
    "@radix-ui/react-portal/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
1041 +
1042 +
    "@radix-ui/react-roving-focus/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
1043 +
1044 +
    "@radix-ui/react-switch/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
1045 +
1046 +
    "@radix-ui/react-visually-hidden/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
955 1047
956 1048
    "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
957 1049
  }
package.json +19 −18
10 10
		"preview": "vite preview"
11 11
	},
12 12
	"dependencies": {
13 -
		"@evolu/common": "^6.0.1-preview.18",
14 -
		"@evolu/react": "^9.0.1-preview.4",
15 -
		"@evolu/react-web": "^1.0.1-preview.4",
16 -
		"@radix-ui/react-avatar": "^1.1.10",
13 +
		"@evolu/common": "^7.2.0",
14 +
		"@evolu/react": "^10.2.0",
15 +
		"@evolu/react-web": "^2.2.0",
16 +
		"@evolu/web": "^2.2.0",
17 +
		"@radix-ui/react-avatar": "^1.1.11",
17 18
		"@radix-ui/react-collapsible": "^1.1.12",
18 19
		"@radix-ui/react-context-menu": "^2.2.16",
19 20
		"@radix-ui/react-dialog": "^1.1.15",
20 21
		"@radix-ui/react-dropdown-menu": "^2.1.16",
21 -
		"@radix-ui/react-label": "^2.1.7",
22 +
		"@radix-ui/react-label": "^2.1.8",
22 23
		"@radix-ui/react-popover": "^1.1.15",
23 24
		"@radix-ui/react-select": "^2.2.6",
24 -
		"@radix-ui/react-separator": "^1.1.7",
25 -
		"@radix-ui/react-slot": "^1.2.3",
25 +
		"@radix-ui/react-separator": "^1.1.8",
26 +
		"@radix-ui/react-slot": "^1.2.4",
26 27
		"@radix-ui/react-switch": "^1.2.6",
27 28
		"@radix-ui/react-tooltip": "^1.2.8",
28 29
		"@tailwindcss/typography": "^0.5.19",
29 -
		"@tailwindcss/vite": "^4.1.16",
30 +
		"@tailwindcss/vite": "^4.1.17",
30 31
		"class-variance-authority": "^0.7.1",
31 32
		"clsx": "^2.1.1",
32 -
		"fast-xml-parser": "^5.3.1",
33 +
		"fast-xml-parser": "^5.3.2",
33 34
		"lucide-react": "^0.548.0",
34 35
		"next-themes": "^0.4.6",
35 36
		"react": "^19.2.0",
39 40
		"rehype-sanitize": "^6.0.0",
40 41
		"remark-gfm": "^4.0.1",
41 42
		"sonner": "^2.0.7",
42 -
		"tailwind-merge": "^3.3.1",
43 -
		"tailwindcss": "^4.1.16"
43 +
		"tailwind-merge": "^3.4.0",
44 +
		"tailwindcss": "^4.1.17"
44 45
	},
45 46
	"devDependencies": {
46 -
		"@eslint/js": "^9.39.0",
47 -
		"@types/node": "^24.10.0",
48 -
		"@types/react": "^19.2.2",
49 -
		"@types/react-dom": "^19.2.2",
50 -
		"@vitejs/plugin-react": "^5.1.0",
51 -
		"eslint": "^9.39.0",
47 +
		"@eslint/js": "^9.39.1",
48 +
		"@types/node": "^24.10.1",
49 +
		"@types/react": "^19.2.6",
50 +
		"@types/react-dom": "^19.2.3",
51 +
		"@vitejs/plugin-react": "^5.1.1",
52 +
		"eslint": "^9.39.1",
52 53
		"eslint-plugin-react-hooks": "^5.2.0",
53 54
		"eslint-plugin-react-refresh": "^0.4.24",
54 55
		"globals": "^16.5.0",
55 56
		"tw-animate-css": "^1.4.0",
56 57
		"typescript": "~5.9.3",
57 -
		"typescript-eslint": "^8.46.2",
58 +
		"typescript-eslint": "^8.47.0",
58 59
		"vite": "npm:rolldown-vite@7.1.14"
59 60
	},
60 61
	"overrides": {
src/App.tsx +98 −8
1 1
import Dashboard from "./components/dashboard";
2 2
import { useQuery } from "@evolu/react";
3 -
import { allFeedsQuery, useEvolu } from "@/lib/evolu";
3 +
import {
4 +
	allFeedsQuery,
5 +
	localAuth,
6 +
	service,
7 +
	useEvolu,
8 +
	ownerIds,
9 +
} from "@/lib/evolu";
4 10
import { Button } from "@/components/ui/button";
5 11
import { Input } from "@/components/ui/input";
6 12
import * as React from "react";
25 31
	DialogHeader,
26 32
	DialogTitle,
27 33
} from "@/components/ui/dialog";
28 -
import { Upload, FileUp, Info } from "lucide-react";
29 -
import { Mnemonic } from "@evolu/common";
34 +
import { Upload, FileUp, Info, LogIn, Key } from "lucide-react";
35 +
import * as Evolu from "@evolu/common";
30 36
import { LoadingScreen } from "@/components/loading-screen";
31 37
import { AboutDialog } from "@/components/about-dialog";
38 +
import { formatTypeError } from "@/lib/format-error";
32 39
33 40
function App() {
34 41
	const allFeeds = useQuery(allFeedsQuery);
41 48
	const [isAddingFeed, setIsAddingFeed] = React.useState(false);
42 49
	const [errorMessage, setErrorMessage] = React.useState("");
43 50
	const [isRestoreDialogOpen, setIsRestoreDialogOpen] = React.useState(false);
51 +
	const [isPasskeyDialogOpen, setIsPasskeyDialogOpen] = React.useState(false);
44 52
	const [restoreMnemonic, setRestoreMnemonic] = React.useState("");
45 53
	const [isImportingOPML, setIsImportingOPML] = React.useState(false);
46 54
	const fileInputRef = React.useRef<HTMLInputElement>(null);
47 55
48 56
	const evolu = useEvolu();
49 57
58 +
	// Filter available passkeys (if any exist)
59 +
	const availablePasskeys = React.useMemo(() => ownerIds, []);
60 +
50 61
	// Handle initial loading state
51 62
	React.useEffect(() => {
52 63
		// Add a small delay to prevent flash, then stop loading
73 84
74 85
	function handleRestore() {
75 86
		if (restoreMnemonic.trim()) {
76 -
			evolu.restoreAppOwner(restoreMnemonic as Mnemonic);
87 +
			const result = Evolu.Mnemonic.from(restoreMnemonic.trim());
88 +
			if (!result.ok) {
89 +
				toast.error(formatTypeError(result.error));
90 +
				return;
91 +
			}
92 +
93 +
			void evolu.restoreAppOwner(result.value);
77 94
			setIsRestoreDialogOpen(false);
78 95
			setRestoreMnemonic("");
79 96
			toast.success("Account restored successfully");
80 97
		}
81 98
	}
82 99
100 +
	async function handleLoginWithPasskey(ownerId: Evolu.OwnerId) {
101 +
		const result = await localAuth.login(ownerId, { service });
102 +
		if (result) {
103 +
			evolu.reloadApp();
104 +
		} else {
105 +
			toast.error("Failed to login with passkey");
106 +
		}
107 +
	}
108 +
109 +
	function openPasskeyDialog() {
110 +
		if (availablePasskeys.length === 0) {
111 +
			toast.error("No passkeys found on this device");
112 +
			return;
113 +
		}
114 +
		setIsPasskeyDialogOpen(true);
115 +
	}
116 +
83 117
	async function handleImportOPML(file: File) {
84 118
		setIsImportingOPML(true);
85 119
		const importToast = toast.loading("Reading OPML file...");
119 153
					});
120 154
121 155
					if (!result.ok) {
122 -
						throw new Error("Failed to insert feed into database");
156 +
						throw new Error(formatTypeError(result.error));
123 157
					}
124 158
125 159
					for (const post of posts) {
130 164
							feedData.title,
131 165
						);
132 166
133 -
						evolu.insert("rssPost", {
167 +
						const postResult = evolu.insert("rssPost", {
134 168
							title: sanitizedPost.title,
135 169
							author: sanitizedPost.author || null,
136 170
							feedTitle: sanitizedFeed.title,
139 173
							feedId: result.value.id,
140 174
							content: extractPostContent(post, sanitizedPost.link),
141 175
						});
176 +
177 +
						if (!postResult.ok) {
178 +
							console.warn(
179 +
								"Failed to insert post:",
180 +
								formatTypeError(postResult.error),
181 +
							);
182 +
						}
142 183
					}
143 184
144 185
					successCount++;
258 299
			});
259 300
260 301
			if (!result.ok) {
261 -
				throw new Error("Failed to insert feed");
302 +
				throw new Error(formatTypeError(result.error));
262 303
			}
263 304
264 305
			for (const post of posts) {
265 306
				// Sanitize post data to meet schema constraints
266 307
				const sanitizedPost = sanitizePostData(post, isAtom, feedData.title);
267 308
268 -
				evolu.insert("rssPost", {
309 +
				const postResult = evolu.insert("rssPost", {
269 310
					title: sanitizedPost.title,
270 311
					author: sanitizedPost.author || null,
271 312
					feedTitle: sanitizedFeed.title,
274 315
					feedId: result.value.id,
275 316
					content: extractPostContent(post, sanitizedPost.link),
276 317
				});
318 +
319 +
				if (!postResult.ok) {
320 +
					console.warn(
321 +
						"Failed to insert post:",
322 +
						formatTypeError(postResult.error),
323 +
					);
324 +
				}
277 325
			}
278 326
279 327
			toast.success(
362 410
							<Upload className="h-4 w-4 mr-2" />
363 411
							Restore from Backup
364 412
						</Button>
413 +
						{availablePasskeys.length > 0 && (
414 +
							<Button
415 +
								variant="outline"
416 +
								onClick={openPasskeyDialog}
417 +
								className="w-full"
418 +
							>
419 +
								<Key className="h-4 w-4 mr-2" />
420 +
								Login with Passkey
421 +
							</Button>
422 +
						)}
365 423
					</div>
366 424
				</div>
367 425
			)}
392 450
							<Upload className="h-4 w-4 mr-2" />
393 451
							Restore Account
394 452
						</Button>
453 +
					</div>
454 +
				</DialogContent>
455 +
			</Dialog>
456 +
			<Dialog open={isPasskeyDialogOpen} onOpenChange={setIsPasskeyDialogOpen}>
457 +
				<DialogContent>
458 +
					<DialogHeader>
459 +
						<DialogTitle>Login with Passkey</DialogTitle>
460 +
						<DialogDescription>
461 +
							Select a passkey to authenticate and access your encrypted data.
462 +
						</DialogDescription>
463 +
					</DialogHeader>
464 +
					<div className="space-y-3">
465 +
						{availablePasskeys.map(({ ownerId, username }) => (
466 +
							<div
467 +
								key={ownerId}
468 +
								className="flex items-center justify-between p-3 bg-muted rounded-lg"
469 +
							>
470 +
								<div className="flex flex-col">
471 +
									<span className="text-sm font-medium">{username}</span>
472 +
									<span className="text-xs text-muted-foreground truncate max-w-[200px]">
473 +
										{ownerId}
474 +
									</span>
475 +
								</div>
476 +
								<Button
477 +
									size="sm"
478 +
									onClick={() => handleLoginWithPasskey(ownerId)}
479 +
								>
480 +
									<LogIn className="h-3 w-3 mr-1" />
481 +
									Login
482 +
								</Button>
483 +
							</div>
484 +
						))}
395 485
					</div>
396 486
				</DialogContent>
397 487
			</Dialog>
src/components/add-feed-dialog.tsx +10 −2
25 25
	isYouTubeUrl,
26 26
	convertYouTubeUrlToFeed,
27 27
} from "@/lib/feed-operations";
28 +
import { formatTypeError } from "@/lib/format-error";
28 29
29 30
interface AddFeedDialogProps {
30 31
	open: boolean;
101 102
			});
102 103
103 104
			if (!result.ok) {
104 -
				throw new Error("Failed to insert feed");
105 +
				throw new Error(formatTypeError(result.error));
105 106
			}
106 107
107 108
			// Process posts/entries
109 110
				// Sanitize post data to meet schema constraints
110 111
				const sanitizedPost = sanitizePostData(post, isAtom, feedData.title);
111 112
112 -
				evolu.insert("rssPost", {
113 +
				const postResult = evolu.insert("rssPost", {
113 114
					title: sanitizedPost.title,
114 115
					author: sanitizedPost.author || null,
115 116
					feedTitle: sanitizedFeed.title,
118 119
					feedId: result.value.id,
119 120
					content: extractPostContent(post, sanitizedPost.link),
120 121
				});
122 +
123 +
				if (!postResult.ok) {
124 +
					console.warn(
125 +
						"Failed to insert post:",
126 +
						formatTypeError(postResult.error),
127 +
					);
128 +
				}
121 129
			}
122 130
123 131
			toast.success(
src/components/app-sidebar.tsx +42 −13
36 36
	extractPostContent,
37 37
	extractPostDate,
38 38
} from "@/lib/feed-operations";
39 +
import { formatTypeError } from "@/lib/format-error";
39 40
40 41
interface AppSidebarProps extends React.ComponentProps<typeof Sidebar> {
41 42
	selectedFeedId?: string | null;
142 143
143 144
			if (existingStatus) {
144 145
				// Update existing status to read
145 -
				evolu.update("readStatus", {
146 +
				const updateResult = evolu.update("readStatus", {
146 147
					id: existingStatus.id as any,
147 148
					isRead: 1,
148 149
				});
150 +
				if (!updateResult.ok) {
151 +
					console.warn(
152 +
						"Failed to update read status:",
153 +
						formatTypeError(updateResult.error),
154 +
					);
155 +
				}
149 156
			} else if (post && post.feedId) {
150 157
				// Create new read status
151 -
				evolu.insert("readStatus", {
158 +
				const insertResult = evolu.insert("readStatus", {
152 159
					postId: postId,
153 160
					feedId: post.feedId,
154 161
					isRead: 1,
155 162
				});
163 +
				if (!insertResult.ok) {
164 +
					console.warn(
165 +
						"Failed to insert read status:",
166 +
						formatTypeError(insertResult.error),
167 +
					);
168 +
				}
156 169
			}
157 170
158 171
			// Call the original onPostSelect
184 197
185 198
			if (existingStatus && !existingStatus.isRead) {
186 199
				// Update existing status to read
187 -
				evolu.update("readStatus", {
200 +
				const updateResult = evolu.update("readStatus", {
188 201
					id: existingStatus.id as any,
189 202
					isRead: 1,
190 203
				});
191 -
				markedCount++;
204 +
				if (updateResult.ok) {
205 +
					markedCount++;
206 +
				}
192 207
			} else if (!existingStatus && post.feedId) {
193 208
				// Create new read status
194 -
				evolu.insert("readStatus", {
209 +
				const insertResult = evolu.insert("readStatus", {
195 210
					postId: post.id,
196 211
					feedId: post.feedId,
197 212
					isRead: 1,
198 213
				});
199 -
				markedCount++;
214 +
				if (insertResult.ok) {
215 +
					markedCount++;
216 +
				}
200 217
			}
201 218
		});
202 219
		toast.success(
214 231
215 232
			if (existingStatus && existingStatus.isRead) {
216 233
				// Update existing status to unread
217 -
				evolu.update("readStatus", {
234 +
				const updateResult = evolu.update("readStatus", {
218 235
					id: existingStatus.id as any,
219 236
					isRead: 0,
220 237
				});
221 -
				unmarkedCount++;
238 +
				if (updateResult.ok) {
239 +
					unmarkedCount++;
240 +
				}
222 241
			} else if (!existingStatus && post.feedId) {
223 242
				// Create new unread status
224 -
				evolu.insert("readStatus", {
243 +
				const insertResult = evolu.insert("readStatus", {
225 244
					postId: post.id,
226 245
					feedId: post.feedId,
227 246
					isRead: 0,
228 247
				});
229 -
				unmarkedCount++;
248 +
				if (insertResult.ok) {
249 +
					unmarkedCount++;
250 +
				}
230 251
			}
231 252
		});
232 253
		toast.success(
408 429
							continue;
409 430
						}
410 431
411 -
						evolu.insert("rssPost", {
432 +
						const postResult = evolu.insert("rssPost", {
412 433
							title: post.title,
413 434
							author: extractPostAuthor(post, isAtom, feedData.title),
414 435
							feedTitle: feed.title,
417 438
							feedId: feed.id,
418 439
							content: extractPostContent(post, postLink),
419 440
						});
420 -
						newPostsCount++;
441 +
						if (postResult.ok) {
442 +
							newPostsCount++;
443 +
						}
421 444
					}
422 445
423 446
					totalNewPosts += newPostsCount;
424 447
425 448
					// Update feed's dateUpdated
426 -
					evolu.update("rssFeed", {
449 +
					const updateResult = evolu.update("rssFeed", {
427 450
						id: feed.id as any,
428 451
						dateUpdated: new Date().toISOString(),
429 452
					});
453 +
					if (!updateResult.ok) {
454 +
						console.warn(
455 +
							"Failed to update feed dateUpdated:",
456 +
							formatTypeError(updateResult.error),
457 +
						);
458 +
					}
430 459
				} catch (error) {
431 460
					console.error(`Error refreshing feed "${feed.title}":`, error);
432 461
					// Continue with other feeds even if one fails
src/components/nav-user.tsx +182 −7
8 8
	Upload,
9 9
	Download,
10 10
	FileUp,
11 +
	Key,
12 +
	LogIn,
11 13
} from "lucide-react";
12 14
import {
13 15
	Dialog,
18 20
	DialogTrigger,
19 21
} from "@/components/ui/dialog";
20 22
import { Button } from "@/components/ui/button";
21 -
import { use, useState, useRef } from "react";
22 -
import { useEvolu, reset, allFeedsQuery } from "@/lib/evolu";
23 +
import { use, useState, useRef, useMemo } from "react";
24 +
import {
25 +
	useEvolu,
26 +
	reset,
27 +
	allFeedsQuery,
28 +
	localAuth,
29 +
	service,
30 +
	ownerIds,
31 +
	authResult,
32 +
} from "@/lib/evolu";
23 33
import { useQuery } from "@evolu/react";
24 34
import { generateOPML, parseOPML, downloadOPML } from "@/lib/opml";
25 35
import {
45 55
	SidebarMenuItem,
46 56
	useSidebar,
47 57
} from "@/components/ui/sidebar";
48 -
import { Mnemonic } from "@evolu/common";
58 +
import * as Evolu from "@evolu/common";
49 59
import { AboutDialog } from "@/components/about-dialog";
60 +
import { formatTypeError } from "@/lib/format-error";
50 61
51 62
export function NavUser() {
52 63
	const { isMobile } = useSidebar();
54 65
	const [isRestoreDialogOpen, setIsRestoreDialogOpen] = useState(false);
55 66
	const [isAboutDialogOpen, setIsAboutDialogOpen] = useState(false);
56 67
	const [isImportOPMLDialogOpen, setIsImportOPMLDialogOpen] = useState(false);
57 -
	const [backupPhrase, setBackupPhrase] = useState<Mnemonic | null>();
68 +
	const [isPasskeyDialogOpen, setIsPasskeyDialogOpen] = useState(false);
69 +
	const [backupPhrase, setBackupPhrase] = useState<Evolu.Mnemonic | null>();
58 70
	const [isRevealed, setIsRevealed] = useState(false);
59 71
	const [isCopied, setIsCopied] = useState(false);
60 72
	const [restoreMnemonic, setRestoreMnemonic] = useState("");
75 87
	const owner = use(evolu.appOwner);
76 88
	const feeds = useQuery(allFeedsQuery);
77 89
90 +
	// Get other registered passkey profiles
91 +
	const otherOwnerIds = useMemo(
92 +
		() => ownerIds.filter(({ ownerId }) => ownerId !== owner?.id),
93 +
		[owner?.id],
94 +
	);
95 +
78 96
	function backup() {
79 -
		setBackupPhrase(owner.mnemonic);
97 +
		setBackupPhrase(owner?.mnemonic);
98 +
	}
99 +
100 +
	// Passkey registration
101 +
	async function handleRegisterPasskey() {
102 +
		const username = window.prompt("Enter your username for passkey:");
103 +
		if (username == null) return;
104 +
105 +
		// Determine if this is a guest login or a new owner
106 +
		const isGuest = !Boolean(authResult?.owner);
107 +
108 +
		// Register the guest owner or create a new one if already registered
109 +
		const result = await localAuth.register(username, {
110 +
			service: service,
111 +
			mnemonic: isGuest ? owner?.mnemonic : undefined,
112 +
		});
113 +
114 +
		if (result) {
115 +
			// If this is a guest owner, clear the database and reload
116 +
			// The owner is transferred to a new database on next login
117 +
			if (isGuest) {
118 +
				void evolu.resetAppOwner({ reload: true });
119 +
			} else {
120 +
				// Otherwise, just reload the page
121 +
				evolu.reloadApp();
122 +
			}
123 +
		} else {
124 +
			alert(
125 +
				"Failed to register passkey. Make sure your device supports passkeys.",
126 +
			);
127 +
		}
128 +
	}
129 +
130 +
	// Passkey login
131 +
	async function handleLoginWithPasskey(ownerId: Evolu.OwnerId) {
132 +
		const result = await localAuth.login(ownerId, { service });
133 +
		if (result) {
134 +
			evolu.reloadApp();
135 +
		} else {
136 +
			alert("Failed to login with passkey");
137 +
		}
138 +
	}
139 +
140 +
	// Clear all passkeys and data
141 +
	async function handleClearAllPasskeys() {
142 +
		const confirmed = window.confirm(
143 +
			"Are you sure you want to clear all passkeys and data? This cannot be undone.",
144 +
		);
145 +
		if (!confirmed) return;
146 +
147 +
		await localAuth.clearAll({ service });
148 +
		void evolu.resetAppOwner({ reload: true });
80 149
	}
81 150
82 151
	async function handleExportOPML() {
130 199
131 200
					for (const post of posts) {
132 201
						const postLink = extractPostLink(post, isAtom);
133 -
						evolu.insert("rssPost", {
202 +
						const postResult = evolu.insert("rssPost", {
134 203
							title: post.title,
135 204
							author: extractPostAuthor(post, isAtom, feedData.title),
136 205
							feedTitle: feed.title,
139 208
							feedId: result.value.id,
140 209
							content: extractPostContent(post, postLink),
141 210
						});
211 +
						if (!postResult.ok) {
212 +
							console.warn(
213 +
								"Failed to insert post:",
214 +
								formatTypeError(postResult.error),
215 +
							);
216 +
						}
142 217
					}
143 218
144 219
					successCount++;
201 276
202 277
	function handleRestore() {
203 278
		if (restoreMnemonic.trim()) {
204 -
			evolu.restoreAppOwner(restoreMnemonic as Mnemonic);
279 +
			const result = Evolu.Mnemonic.from(restoreMnemonic.trim());
280 +
			if (!result.ok) {
281 +
				alert(formatTypeError(result.error));
282 +
				return;
283 +
			}
284 +
285 +
			void evolu.restoreAppOwner(result.value);
205 286
			setIsRestoreDialogOpen(false);
206 287
			setRestoreMnemonic("");
207 288
		}
237 318
								</DropdownMenuGroup>
238 319
								<DropdownMenuSeparator />
239 320
								<DropdownMenuGroup>
321 +
									<DropdownMenuItem
322 +
										onClick={() => setIsPasskeyDialogOpen(true)}
323 +
									>
324 +
										<Key />
325 +
										Passkeys
326 +
									</DropdownMenuItem>
327 +
								</DropdownMenuGroup>
328 +
								<DropdownMenuSeparator />
329 +
								<DropdownMenuGroup>
240 330
									<DialogTrigger asChild>
241 331
										<DropdownMenuItem>
242 332
											<BookKey />
382 472
								</p>
383 473
							</>
384 474
						)}
475 +
					</div>
476 +
				</DialogContent>
477 +
			</Dialog>
478 +
			<Dialog open={isPasskeyDialogOpen} onOpenChange={setIsPasskeyDialogOpen}>
479 +
				<DialogContent>
480 +
					<DialogHeader>
481 +
						<DialogTitle>Passkey Management</DialogTitle>
482 +
						<DialogDescription>
483 +
							Register a passkey to securely access your account across devices
484 +
							without entering a mnemonic. Your device's biometric
485 +
							authentication (fingerprint, face ID, etc.) will protect your
486 +
							data.
487 +
						</DialogDescription>
488 +
					</DialogHeader>
489 +
					<div className="space-y-4">
490 +
						{owner && (
491 +
							<div className="p-3 bg-muted rounded-lg">
492 +
								<p className="text-xs font-medium text-muted-foreground mb-1">
493 +
									Current Account
494 +
								</p>
495 +
								<p className="text-sm font-medium">
496 +
									{authResult?.username ?? "Guest"}
497 +
								</p>
498 +
								<p className="text-xs text-muted-foreground mt-1">{owner.id}</p>
499 +
							</div>
500 +
						)}
501 +
502 +
						<div className="flex gap-2">
503 +
							<Button
504 +
								onClick={handleRegisterPasskey}
505 +
								className="flex-1"
506 +
								variant="default"
507 +
							>
508 +
								<Key className="h-4 w-4 mr-2" />
509 +
								Register Passkey
510 +
							</Button>
511 +
							<Button
512 +
								onClick={handleClearAllPasskeys}
513 +
								className="flex-1"
514 +
								variant="destructive"
515 +
							>
516 +
								<Trash2 className="h-4 w-4 mr-2" />
517 +
								Clear All
518 +
							</Button>
519 +
						</div>
520 +
521 +
						{otherOwnerIds.length > 0 && (
522 +
							<>
523 +
								<div className="border-t pt-4">
524 +
									<p className="text-sm font-medium mb-3">
525 +
										Other Registered Passkeys
526 +
									</p>
527 +
									<div className="space-y-2">
528 +
										{otherOwnerIds.map(({ ownerId, username }) => (
529 +
											<div
530 +
												key={ownerId}
531 +
												className="flex items-center justify-between p-3 bg-muted rounded-lg"
532 +
											>
533 +
												<div className="flex flex-col">
534 +
													<span className="text-sm font-medium">
535 +
														{username}
536 +
													</span>
537 +
													<span className="text-xs text-muted-foreground">
538 +
														{ownerId}
539 +
													</span>
540 +
												</div>
541 +
												<Button
542 +
													size="sm"
543 +
													variant="outline"
544 +
													onClick={() => handleLoginWithPasskey(ownerId)}
545 +
												>
546 +
													<LogIn className="h-3 w-3 mr-1" />
547 +
													Login
548 +
												</Button>
549 +
											</div>
550 +
										))}
551 +
									</div>
552 +
								</div>
553 +
							</>
554 +
						)}
555 +
556 +
						<p className="text-xs text-muted-foreground">
557 +
							💡 Passkeys use your device's secure enclave for authentication.
558 +
							You can register multiple passkeys for different devices or users.
559 +
						</p>
385 560
					</div>
386 561
				</DialogContent>
387 562
			</Dialog>
src/lib/evolu.ts +43 −26
1 -
import { evoluReactWebDeps } from "@evolu/react-web";
1 +
import * as Evolu from "@evolu/common";
2 +
import { createUseEvolu } from "@evolu/react";
3 +
import { evoluReactWebDeps, localAuth } from "@evolu/react-web";
2 4
import { Schema, type RSSFeedId } from "./scheme.ts";
3 -
import { createUseEvolu } from "@evolu/react";
4 -
import { createEvolu, SimpleName, sqliteTrue } from "@evolu/common";
5 +
6 +
// Namespace for the current app (scopes databases, passkeys, etc.)
7 +
const service = "alcove";
5 8
6 -
export const evolu = createEvolu(evoluReactWebDeps)(Schema, {
7 -
	name: SimpleName.orThrow("alcove"),
9 +
// Get authentication profiles and initialize owner
10 +
// This is a top-level await for simplicity - in production you may want to handle this differently
11 +
const ownerIds = await localAuth.getProfiles({ service });
12 +
const authResult = await localAuth.getOwner({ service });
13 +
14 +
// Create Evolu instance for the React web platform
15 +
export const evolu = Evolu.createEvolu(evoluReactWebDeps)(Schema, {
16 +
	name: Evolu.SimpleName.orThrow(
17 +
		`${service}-${authResult?.owner?.id ?? "guest"}`,
18 +
	),
8 19
	reloadUrl: "/",
20 +
	encryptionKey: authResult?.owner?.encryptionKey,
21 +
	externalAppOwner: authResult?.owner,
9 22
	transports: [
10 -
		{
11 -
			type: "WebSocket",
12 -
			url: "wss://relay.alcove.tools",
13 -
		},
14 -
		{
15 -
			type: "WebSocket",
16 -
			url: "wss://relay2.alcove.tools",
17 -
		},
23 +
		// {
24 +
		// 	type: "WebSocket",
25 +
		// 	url: "wss://relay.alcove.tools",
26 +
		// },
27 +
		// {
28 +
		// 	type: "WebSocket",
29 +
		// 	url: "wss://relay2.alcove.tools",
30 +
		// },
31 +
		{ type: "WebSocket" as const, url: "ws://localhost:4000" },
18 32
	],
19 33
});
34 +
35 +
// Export authentication utilities and owner info
36 +
export { localAuth, service, ownerIds, authResult };
20 37
21 38
export const useEvolu = createUseEvolu(evolu);
22 39
34 51
	db
35 52
		.selectFrom("rssFeed")
36 53
		.selectAll()
37 -
		.where("isDeleted", "is not", sqliteTrue)
54 +
		.where("isDeleted", "is not", Evolu.sqliteTrue)
38 55
		// Filter out null titles and feedUrls (required fields)
39 56
		.where("title", "is not", null)
40 57
		.where("feedUrl", "is not", null)
41 58
		.$narrowType<{
42 -
			title: import("@evolu/common").kysely.NotNull;
43 -
			feedUrl: import("@evolu/common").kysely.NotNull;
59 +
			title: Evolu.kysely.NotNull;
60 +
			feedUrl: Evolu.kysely.NotNull;
44 61
		}>()
45 62
		.orderBy("createdAt"),
46 63
);
51 68
			.selectFrom("rssPost")
52 69
			.selectAll()
53 70
			.where("feedId", "=", feedId as RSSFeedId)
54 -
			.where("isDeleted", "is not", sqliteTrue)
71 +
			.where("isDeleted", "is not", Evolu.sqliteTrue)
55 72
			// Filter out null required fields
56 73
			.where("title", "is not", null)
57 74
			.where("link", "is not", null)
58 75
			.$narrowType<{
59 -
				title: import("@evolu/common").kysely.NotNull;
60 -
				link: import("@evolu/common").kysely.NotNull;
76 +
				title: Evolu.kysely.NotNull;
77 +
				link: Evolu.kysely.NotNull;
61 78
			}>()
62 79
			.orderBy("id", "desc"),
63 80
	);
66 83
	db
67 84
		.selectFrom("rssPost")
68 85
		.selectAll()
69 -
		.where("isDeleted", "is not", sqliteTrue)
86 +
		.where("isDeleted", "is not", Evolu.sqliteTrue)
70 87
		// Filter out null required fields
71 88
		.where("title", "is not", null)
72 89
		.where("link", "is not", null)
73 90
		.$narrowType<{
74 -
			title: import("@evolu/common").kysely.NotNull;
75 -
			link: import("@evolu/common").kysely.NotNull;
91 +
			title: Evolu.kysely.NotNull;
92 +
			link: Evolu.kysely.NotNull;
76 93
		}>()
77 94
		.orderBy("id", "desc"),
78 95
);
81 98
	db
82 99
		.selectFrom("rssFeed")
83 100
		.selectAll()
84 -
		.where("isDeleted", "is not", sqliteTrue)
101 +
		.where("isDeleted", "is not", Evolu.sqliteTrue)
85 102
		// Filter out null required fields
86 103
		.where("title", "is not", null)
87 104
		.where("feedUrl", "is not", null)
88 105
		.$narrowType<{
89 -
			title: import("@evolu/common").kysely.NotNull;
90 -
			feedUrl: import("@evolu/common").kysely.NotNull;
106 +
			title: Evolu.kysely.NotNull;
107 +
			feedUrl: Evolu.kysely.NotNull;
91 108
		}>()
92 109
		.orderBy("category", "asc"),
93 110
);
101 118
);
102 119
103 120
export function reset() {
104 -
	evolu.resetAppOwner();
121 +
	void evolu.resetAppOwner({ reload: true });
105 122
}
src/lib/format-error.ts (added) +22 −0
1 +
import * as Evolu from "@evolu/common";
2 +
3 +
/**
4 +
 * Formats Evolu Type errors into user-friendly messages.
5 +
 *
6 +
 * Evolu Type typed errors ensure every error type used in schema must have a
7 +
 * formatter. TypeScript enforces this at compile-time, preventing unhandled
8 +
 * validation errors from reaching users.
9 +
 *
10 +
 * The `createFormatTypeError` function handles both built-in and custom errors,
11 +
 * and lets us override default formatting for specific errors.
12 +
 */
13 +
export const formatTypeError = Evolu.createFormatTypeError<
14 +
	Evolu.MinLengthError | Evolu.MaxLengthError
15 +
>((error): string => {
16 +
	switch (error.type) {
17 +
		case "MinLength":
18 +
			return `Text must be at least ${error.min} character${error.min === 1 ? "" : "s"} long`;
19 +
		case "MaxLength":
20 +
			return `Text is too long (maximum ${error.max} characters)`;
21 +
	}
22 +
});
src/lib/scheme.ts +21 −31
1 -
import {
2 -
	id,
3 -
	maxLength,
4 -
	NonEmptyString,
5 -
	NonEmptyString1000,
6 -
	nullOr,
7 -
	Number,
8 -
} from "@evolu/common";
1 +
import * as Evolu from "@evolu/common";
9 2
10 3
// RSS Feed ID
11 -
const RSSFeedId = id("RSSFeed");
4 +
const RSSFeedId = Evolu.id("RSSFeed");
12 5
export type RSSFeedId = typeof RSSFeedId.Type;
13 6
14 7
// RSS Post ID
15 -
const RSSPostId = id("RSSPost");
8 +
const RSSPostId = Evolu.id("RSSPost");
16 9
export type RSSPostId = typeof RSSPostId.Type;
17 10
18 11
// Custom branded types for string length constraints
19 -
const NonEmptyString50 = maxLength(50)(NonEmptyString);
20 -
type NonEmptyString50 = typeof NonEmptyString50.Type;
21 -
22 -
const NonEmptyString200 = maxLength(200)(NonEmptyString);
23 -
type NonEmptyString200 = typeof NonEmptyString200.Type;
12 +
const NonEmptyString50 = Evolu.maxLength(50)(Evolu.NonEmptyString);
13 +
const NonEmptyString200 = Evolu.maxLength(200)(Evolu.NonEmptyString);
24 14
25 15
export const Schema = {
26 16
	rssFeed: {
27 17
		id: RSSFeedId,
28 -
		feedUrl: NonEmptyString1000,
18 +
		feedUrl: Evolu.NonEmptyString1000,
29 19
		title: NonEmptyString200,
30 -
		description: nullOr(NonEmptyString1000),
31 -
		category: nullOr(NonEmptyString50),
32 -
		dateUpdated: nullOr(NonEmptyString),
20 +
		description: Evolu.nullOr(Evolu.NonEmptyString1000),
21 +
		category: Evolu.nullOr(NonEmptyString50),
22 +
		dateUpdated: Evolu.nullOr(Evolu.NonEmptyString),
33 23
	},
34 24
	rssPost: {
35 25
		id: RSSPostId,
36 26
		feedId: RSSFeedId,
37 -
		title: NonEmptyString1000,
38 -
		link: NonEmptyString1000,
39 -
		content: nullOr(NonEmptyString),
40 -
		author: nullOr(NonEmptyString200),
41 -
		feedTitle: nullOr(NonEmptyString200),
42 -
		publishedDate: nullOr(NonEmptyString),
27 +
		title: Evolu.NonEmptyString1000,
28 +
		link: Evolu.NonEmptyString1000,
29 +
		content: Evolu.nullOr(Evolu.NonEmptyString),
30 +
		author: Evolu.nullOr(NonEmptyString200),
31 +
		feedTitle: Evolu.nullOr(NonEmptyString200),
32 +
		publishedDate: Evolu.nullOr(Evolu.NonEmptyString),
43 33
	},
44 34
	readStatus: {
45 -
		id: id("ReadStatus"),
35 +
		id: Evolu.id("ReadStatus"),
46 36
		feedId: RSSFeedId,
47 37
		postId: RSSPostId,
48 -
		isRead: nullOr(Number), // 0 for false, 1 for true
38 +
		isRead: Evolu.nullOr(Evolu.Number), // 0 for false, 1 for true
49 39
	},
50 40
	userPreferences: {
51 -
		id: id("UserPreferences"),
52 -
		theme: nullOr(NonEmptyString50), // Will store "light" or "dark"
53 -
		refreshInterval: nullOr(NonEmptyString50), // Will store number as string
54 -
		postsPerPage: nullOr(NonEmptyString50), // Will store number as string
41 +
		id: Evolu.id("UserPreferences"),
42 +
		theme: Evolu.nullOr(NonEmptyString50), // Will store "light" or "dark"
43 +
		refreshInterval: Evolu.nullOr(NonEmptyString50), // Will store number as string
44 +
		postsPerPage: Evolu.nullOr(NonEmptyString50), // Will store number as string
55 45
	},
56 46
};